799 lines
28 KiB
TypeScript
799 lines
28 KiB
TypeScript
import React, { createContext, useContext, useEffect, useMemo, useState } from "react";
|
|
|
|
export type Language = "en" | "zh";
|
|
|
|
export type TranslationKey =
|
|
| "workspace"
|
|
| "project"
|
|
| "runs"
|
|
| "localDev"
|
|
| "navProjects"
|
|
| "navAssets"
|
|
| "navNodes"
|
|
| "navWorkflows"
|
|
| "navRuns"
|
|
| "navExplore"
|
|
| "navLabels"
|
|
| "navAdmin"
|
|
| "language"
|
|
| "english"
|
|
| "chinese"
|
|
| "assetsTitle"
|
|
| "assetsDescription"
|
|
| "projectsTitle"
|
|
| "projectsDescription"
|
|
| "projectNameLabel"
|
|
| "projectDescriptionLabel"
|
|
| "createProject"
|
|
| "noProjectsYet"
|
|
| "activeProject"
|
|
| "openProject"
|
|
| "storageConnectionsTitle"
|
|
| "storageConnectionsDescription"
|
|
| "createStorageConnection"
|
|
| "storageProvider"
|
|
| "bucket"
|
|
| "endpoint"
|
|
| "region"
|
|
| "basePath"
|
|
| "rootPath"
|
|
| "noStorageConnectionsYet"
|
|
| "nodesTitle"
|
|
| "nodesDescription"
|
|
| "createCustomNode"
|
|
| "noCustomNodesYet"
|
|
| "customNodeName"
|
|
| "customNodeDescription"
|
|
| "customNodeCategory"
|
|
| "customNodeSourceKind"
|
|
| "customNodeSourceImage"
|
|
| "customNodeSourceDockerfile"
|
|
| "customNodeImage"
|
|
| "customNodeDockerfile"
|
|
| "customNodeDockerfileUpload"
|
|
| "customNodeCommand"
|
|
| "customNodeInputMode"
|
|
| "customNodeOutputMode"
|
|
| "customNodeArtifactType"
|
|
| "customNodeSingleAssetSet"
|
|
| "customNodeMultiAssetSet"
|
|
| "customNodeReport"
|
|
| "customNodeAssetSet"
|
|
| "customNodeAssetSetWithReport"
|
|
| "datasetsTitle"
|
|
| "datasetsDescription"
|
|
| "datasetName"
|
|
| "datasetDescription"
|
|
| "sourceAsset"
|
|
| "sourceAssets"
|
|
| "storageConnection"
|
|
| "storagePathLabel"
|
|
| "createDataset"
|
|
| "noDatasetsYet"
|
|
| "latestDatasetVersion"
|
|
| "localPath"
|
|
| "registerLocalPath"
|
|
| "noAssetsYet"
|
|
| "type"
|
|
| "source"
|
|
| "detected"
|
|
| "pending"
|
|
| "topLevelEntries"
|
|
| "notAvailable"
|
|
| "loadingAssetDetail"
|
|
| "assetId"
|
|
| "status"
|
|
| "sourcePath"
|
|
| "probeAgain"
|
|
| "createExploreArtifact"
|
|
| "openExploreView"
|
|
| "probeSummary"
|
|
| "warnings"
|
|
| "none"
|
|
| "recommendedNodes"
|
|
| "noProbeReportYet"
|
|
| "workflowsTitle"
|
|
| "workflowTemplatesTitle"
|
|
| "workflowTemplatesDescription"
|
|
| "createWorkflow"
|
|
| "createBlankWorkflow"
|
|
| "createWorkflowFromTemplate"
|
|
| "saveAsTemplate"
|
|
| "templateName"
|
|
| "templateDescription"
|
|
| "templateSaved"
|
|
| "noWorkflowTemplatesYet"
|
|
| "noWorkflowsYet"
|
|
| "latestVersion"
|
|
| "workflowEditor"
|
|
| "runAsset"
|
|
| "saveWorkflowVersion"
|
|
| "triggerWorkflowRun"
|
|
| "reloadLatestSaved"
|
|
| "openLatestRun"
|
|
| "selectAssetBeforeRun"
|
|
| "nodeLibrary"
|
|
| "nodeLibraryHint"
|
|
| "canvas"
|
|
| "canvasHint"
|
|
| "dragNodeToCanvas"
|
|
| "latestSavedVersions"
|
|
| "draftStatus"
|
|
| "draftSynced"
|
|
| "draftUnsaved"
|
|
| "nodeConfiguration"
|
|
| "category"
|
|
| "definition"
|
|
| "executorType"
|
|
| "runtimeTarget"
|
|
| "artifactTitle"
|
|
| "pythonCodeHook"
|
|
| "nodeNoHook"
|
|
| "selectNode"
|
|
| "removeNode"
|
|
| "workflowCreatedName"
|
|
| "noAssetsAvailable"
|
|
| "runsTitle"
|
|
| "runsDescription"
|
|
| "noRunsYet"
|
|
| "createdAt"
|
|
| "inputAssets"
|
|
| "runDetail"
|
|
| "workflow"
|
|
| "startedAt"
|
|
| "finishedAt"
|
|
| "runDuration"
|
|
| "runSummary"
|
|
| "cancelRun"
|
|
| "retryRun"
|
|
| "runGraph"
|
|
| "boundAssets"
|
|
| "selectedTask"
|
|
| "executor"
|
|
| "executorConfig"
|
|
| "codeHook"
|
|
| "duration"
|
|
| "summary"
|
|
| "error"
|
|
| "retryTask"
|
|
| "artifacts"
|
|
| "noTaskArtifactsYet"
|
|
| "stdout"
|
|
| "stderr"
|
|
| "executionLog"
|
|
| "resultPreview"
|
|
| "noStdout"
|
|
| "noStderr"
|
|
| "noTaskLogs"
|
|
| "noTasksCreated"
|
|
| "loadingRun"
|
|
| "exploreTitle"
|
|
| "exploreEmpty"
|
|
| "loadingArtifact"
|
|
| "bootstrappingLocalWorkspace"
|
|
| "failedLoadAssets"
|
|
| "failedLoadStorageConnections"
|
|
| "failedCreateStorageConnection"
|
|
| "failedLoadDatasets"
|
|
| "failedCreateDataset"
|
|
| "failedLoadCustomNodes"
|
|
| "failedCreateCustomNode"
|
|
| "failedRegisterAsset"
|
|
| "failedLoadWorkflows"
|
|
| "failedLoadTemplates"
|
|
| "failedCreateTemplate"
|
|
| "failedCreateWorkflowFromTemplate"
|
|
| "failedLoadWorkflow"
|
|
| "failedLoadRuns"
|
|
| "failedLoadRunDetail"
|
|
| "failedLoadTaskArtifacts"
|
|
| "failedCancelRun"
|
|
| "failedRetryRun"
|
|
| "failedRetryTask"
|
|
| "failedLoadArtifact"
|
|
| "failedBootstrap"
|
|
| "failedLoadProjects"
|
|
| "failedCreateProject"
|
|
| "validatedAssetCount"
|
|
| "loadedAssetCount"
|
|
| "success"
|
|
| "failed"
|
|
| "running"
|
|
| "queued"
|
|
| "cancelled"
|
|
| "totalTasks"
|
|
| "stdoutLines"
|
|
| "stderrLines"
|
|
| "successCount"
|
|
| "failedCount"
|
|
| "runningCount"
|
|
| "cancelledCount"
|
|
| "artifactsCount"
|
|
| "viaExecutor"
|
|
| "assetCount"
|
|
| "artifactCount"
|
|
| "invalidConnectionMissingEndpoint"
|
|
| "invalidConnectionSelf"
|
|
| "invalidConnectionDuplicate"
|
|
| "invalidConnectionSourceDisallowsOutgoing"
|
|
| "invalidConnectionTargetDisallowsIncoming"
|
|
| "invalidConnectionTargetAlreadyHasIncoming"
|
|
| "invalidConnectionCycle";
|
|
|
|
const TRANSLATIONS: Record<Language, Record<TranslationKey, string>> = {
|
|
en: {
|
|
workspace: "Workspace",
|
|
project: "Project",
|
|
runs: "Runs",
|
|
localDev: "Local Dev",
|
|
navProjects: "Projects",
|
|
navAssets: "Assets",
|
|
navNodes: "Nodes",
|
|
navWorkflows: "Workflows",
|
|
navRuns: "Runs",
|
|
navExplore: "Explore",
|
|
navLabels: "Labels",
|
|
navAdmin: "Admin",
|
|
language: "Language",
|
|
english: "English",
|
|
chinese: "中文",
|
|
assetsTitle: "Assets",
|
|
assetsDescription:
|
|
"Register local folders, archives, or dataset files, then probe them into managed asset metadata.",
|
|
projectsTitle: "Projects",
|
|
projectsDescription:
|
|
"Create project spaces, switch the active project, and manage project-scoped assets, datasets, workflows, and runs.",
|
|
projectNameLabel: "Project Name",
|
|
projectDescriptionLabel: "Project Description",
|
|
createProject: "Create Project",
|
|
noProjectsYet: "No projects yet.",
|
|
activeProject: "Active project",
|
|
openProject: "Open Project",
|
|
storageConnectionsTitle: "Storage Connections",
|
|
storageConnectionsDescription:
|
|
"Define where project datasets are stored, including local paths and object storage providers.",
|
|
nodesTitle: "Custom Nodes",
|
|
nodesDescription:
|
|
"Register project-level docker nodes from an image or a self-contained Dockerfile. Containers must read the EmboFlow input path and write a result object to the EmboFlow output path.",
|
|
createCustomNode: "Create Custom Node",
|
|
noCustomNodesYet: "No custom nodes have been created yet.",
|
|
customNodeName: "Node Name",
|
|
customNodeDescription: "Node Description",
|
|
customNodeCategory: "Node Category",
|
|
customNodeSourceKind: "Container Source",
|
|
customNodeSourceImage: "Docker Image",
|
|
customNodeSourceDockerfile: "Dockerfile",
|
|
customNodeImage: "Image",
|
|
customNodeDockerfile: "Dockerfile Content",
|
|
customNodeDockerfileUpload: "Upload Dockerfile",
|
|
customNodeCommand: "Command (one argument per line)",
|
|
customNodeInputMode: "Input Contract",
|
|
customNodeOutputMode: "Output Contract",
|
|
customNodeArtifactType: "Artifact Type",
|
|
customNodeSingleAssetSet: "Single asset set",
|
|
customNodeMultiAssetSet: "Multiple asset sets",
|
|
customNodeReport: "Report only",
|
|
customNodeAssetSet: "Asset set",
|
|
customNodeAssetSetWithReport: "Asset set with report",
|
|
createStorageConnection: "Create Storage Connection",
|
|
storageProvider: "Storage Provider",
|
|
bucket: "Bucket",
|
|
endpoint: "Endpoint",
|
|
region: "Region",
|
|
basePath: "Base Path",
|
|
rootPath: "Root Path",
|
|
noStorageConnectionsYet: "No storage connections yet.",
|
|
datasetsTitle: "Datasets",
|
|
datasetsDescription:
|
|
"Create project datasets from source assets and bind them to a storage connection.",
|
|
datasetName: "Dataset Name",
|
|
datasetDescription: "Dataset Description",
|
|
sourceAsset: "Source Asset",
|
|
sourceAssets: "Source Assets",
|
|
storageConnection: "Storage Connection",
|
|
storagePathLabel: "Storage Path",
|
|
createDataset: "Create Dataset",
|
|
noDatasetsYet: "No datasets have been created yet.",
|
|
latestDatasetVersion: "Latest dataset version",
|
|
localPath: "Local Path",
|
|
registerLocalPath: "Register Local Path",
|
|
noAssetsYet: "No assets have been registered yet.",
|
|
type: "Type",
|
|
source: "Source",
|
|
detected: "Detected",
|
|
pending: "pending",
|
|
topLevelEntries: "Top-level entries",
|
|
notAvailable: "n/a",
|
|
loadingAssetDetail: "Loading asset detail...",
|
|
assetId: "Asset ID",
|
|
status: "Status",
|
|
sourcePath: "Source path",
|
|
probeAgain: "Probe Again",
|
|
createExploreArtifact: "Create Explore Artifact",
|
|
openExploreView: "Open Explore View",
|
|
probeSummary: "Probe Summary",
|
|
warnings: "Warnings",
|
|
none: "none",
|
|
recommendedNodes: "Recommended nodes",
|
|
noProbeReportYet: "No probe report yet.",
|
|
workflowsTitle: "Workflows",
|
|
workflowTemplatesTitle: "Workflow Templates",
|
|
workflowTemplatesDescription:
|
|
"Start workflows from reusable templates or create a blank workflow directly in the project.",
|
|
createWorkflow: "Create Workflow",
|
|
createBlankWorkflow: "Create Blank Workflow",
|
|
createWorkflowFromTemplate: "Create From Template",
|
|
saveAsTemplate: "Save As Template",
|
|
templateName: "Template Name",
|
|
templateDescription: "Template Description",
|
|
templateSaved: "Saved template",
|
|
noWorkflowTemplatesYet: "No workflow templates yet.",
|
|
noWorkflowsYet: "No workflows yet.",
|
|
latestVersion: "Latest version",
|
|
workflowEditor: "Workflow Editor",
|
|
runAsset: "Run Asset",
|
|
saveWorkflowVersion: "Save Workflow Version",
|
|
triggerWorkflowRun: "Trigger Workflow Run",
|
|
reloadLatestSaved: "Reload Latest Saved",
|
|
openLatestRun: "Open Latest Run",
|
|
selectAssetBeforeRun: "Select an asset before triggering a workflow run.",
|
|
nodeLibrary: "Node Library",
|
|
nodeLibraryHint: "Click to append or drag a node onto the canvas.",
|
|
canvas: "Canvas",
|
|
canvasHint: "Drag nodes freely, connect handles, zoom, and pan.",
|
|
dragNodeToCanvas: "Drop the node here to place it on the canvas.",
|
|
latestSavedVersions: "Latest saved versions",
|
|
draftStatus: "Draft status",
|
|
draftSynced: "synced",
|
|
draftUnsaved: "unsaved changes",
|
|
nodeConfiguration: "Node Configuration",
|
|
category: "Category",
|
|
definition: "Definition",
|
|
executorType: "Executor Type",
|
|
runtimeTarget: "Runtime Target",
|
|
artifactTitle: "Artifact Title",
|
|
pythonCodeHook: "Python Code Hook",
|
|
nodeNoHook: "This node does not expose a code hook in V1.",
|
|
selectNode: "Select a node.",
|
|
removeNode: "Remove Node",
|
|
workflowCreatedName: "Delivery Normalize {count}",
|
|
noAssetsAvailable: "No assets available",
|
|
runsTitle: "Runs",
|
|
runsDescription: "Recent workflow executions for the current project.",
|
|
noRunsYet: "No workflow runs yet.",
|
|
createdAt: "Created at",
|
|
inputAssets: "Input assets",
|
|
runDetail: "Run Detail",
|
|
workflow: "Workflow",
|
|
startedAt: "Started at",
|
|
finishedAt: "Finished at",
|
|
runDuration: "Run duration",
|
|
runSummary: "Run summary",
|
|
cancelRun: "Cancel Run",
|
|
retryRun: "Retry Run",
|
|
runGraph: "Run Graph",
|
|
boundAssets: "Bound assets",
|
|
selectedTask: "Selected Task",
|
|
executor: "Executor",
|
|
executorConfig: "Executor config",
|
|
codeHook: "Code hook",
|
|
duration: "Duration",
|
|
summary: "Summary",
|
|
error: "Error",
|
|
retryTask: "Retry Task",
|
|
artifacts: "Artifacts",
|
|
noTaskArtifactsYet: "No task artifacts yet.",
|
|
stdout: "Stdout",
|
|
stderr: "Stderr",
|
|
executionLog: "Execution Log",
|
|
resultPreview: "Result Preview",
|
|
noStdout: "No stdout lines.",
|
|
noStderr: "No stderr lines.",
|
|
noTaskLogs: "No task logs yet.",
|
|
noTasksCreated: "No tasks created.",
|
|
loadingRun: "Loading run...",
|
|
exploreTitle: "Explore",
|
|
exploreEmpty: "Create an artifact from asset detail to inspect it here.",
|
|
loadingArtifact: "Loading artifact...",
|
|
bootstrappingLocalWorkspace: "Bootstrapping local workspace...",
|
|
failedLoadAssets: "Failed to load assets",
|
|
failedLoadStorageConnections: "Failed to load storage connections",
|
|
failedCreateStorageConnection: "Failed to create storage connection",
|
|
failedLoadDatasets: "Failed to load datasets",
|
|
failedCreateDataset: "Failed to create dataset",
|
|
failedLoadCustomNodes: "Failed to load custom nodes",
|
|
failedCreateCustomNode: "Failed to create custom node",
|
|
failedRegisterAsset: "Failed to register local asset",
|
|
failedLoadWorkflows: "Failed to load workflows",
|
|
failedLoadTemplates: "Failed to load workflow templates",
|
|
failedCreateTemplate: "Failed to create workflow template",
|
|
failedCreateWorkflowFromTemplate: "Failed to create workflow from template",
|
|
failedLoadWorkflow: "Failed to load workflow",
|
|
failedLoadRuns: "Failed to load runs",
|
|
failedLoadRunDetail: "Failed to load run detail",
|
|
failedLoadTaskArtifacts: "Failed to load task artifacts",
|
|
failedCancelRun: "Failed to cancel run",
|
|
failedRetryRun: "Failed to retry run",
|
|
failedRetryTask: "Failed to retry task",
|
|
failedLoadArtifact: "Failed to load artifact",
|
|
failedBootstrap: "Failed to bootstrap local context",
|
|
failedLoadProjects: "Failed to load projects",
|
|
failedCreateProject: "Failed to create project",
|
|
validatedAssetCount: "validated {count} asset{suffix}",
|
|
loadedAssetCount: "loaded {count} bound asset{suffix}",
|
|
success: "success",
|
|
failed: "failed",
|
|
running: "running",
|
|
queued: "queued",
|
|
cancelled: "cancelled",
|
|
totalTasks: "{count} total tasks",
|
|
stdoutLines: "{count} stdout lines",
|
|
stderrLines: "{count} stderr lines",
|
|
successCount: "{count} success",
|
|
failedCount: "{count} failed",
|
|
runningCount: "{count} running",
|
|
cancelledCount: "{count} cancelled",
|
|
artifactsCount: "artifacts {count}",
|
|
viaExecutor: "{outcome} via {executor}",
|
|
assetCount: "assets {count}",
|
|
artifactCount: "artifacts {count}",
|
|
invalidConnectionMissingEndpoint: "The connection is missing a valid source or target node.",
|
|
invalidConnectionSelf: "A node cannot connect to itself.",
|
|
invalidConnectionDuplicate: "This edge already exists.",
|
|
invalidConnectionSourceDisallowsOutgoing: "Export nodes cannot create outgoing connections in V1.",
|
|
invalidConnectionTargetDisallowsIncoming: "Source nodes cannot accept incoming connections.",
|
|
invalidConnectionTargetAlreadyHasIncoming: "This node already has an upstream connection in V1.",
|
|
invalidConnectionCycle: "This edge would create a cycle.",
|
|
},
|
|
zh: {
|
|
workspace: "工作空间",
|
|
project: "项目",
|
|
runs: "运行",
|
|
localDev: "本地开发",
|
|
navProjects: "项目",
|
|
navAssets: "数据资产",
|
|
navNodes: "节点",
|
|
navWorkflows: "工作流",
|
|
navRuns: "运行记录",
|
|
navExplore: "查看",
|
|
navLabels: "标注",
|
|
navAdmin: "管理",
|
|
language: "语言",
|
|
english: "English",
|
|
chinese: "中文",
|
|
assetsTitle: "数据资产",
|
|
assetsDescription: "注册本地目录、压缩包或数据集文件,并将其探测为受管资产元数据。",
|
|
projectsTitle: "项目",
|
|
projectsDescription: "创建项目、切换当前项目,并管理项目级资产、数据集、工作流与运行记录。",
|
|
projectNameLabel: "项目名称",
|
|
projectDescriptionLabel: "项目描述",
|
|
createProject: "创建项目",
|
|
noProjectsYet: "还没有项目。",
|
|
activeProject: "当前项目",
|
|
openProject: "打开项目",
|
|
storageConnectionsTitle: "存储连接",
|
|
storageConnectionsDescription: "定义项目数据集的存储位置,包括本地路径和对象存储提供方。",
|
|
nodesTitle: "自定义节点",
|
|
nodesDescription:
|
|
"通过镜像或自包含 Dockerfile 注册项目级 Docker 节点。容器必须读取 EmboFlow 输入路径,并把结果对象写入 EmboFlow 输出路径。",
|
|
createCustomNode: "创建自定义节点",
|
|
noCustomNodesYet: "当前还没有自定义节点。",
|
|
customNodeName: "节点名称",
|
|
customNodeDescription: "节点描述",
|
|
customNodeCategory: "节点分类",
|
|
customNodeSourceKind: "容器来源",
|
|
customNodeSourceImage: "Docker 镜像",
|
|
customNodeSourceDockerfile: "Dockerfile",
|
|
customNodeImage: "镜像",
|
|
customNodeDockerfile: "Dockerfile 内容",
|
|
customNodeDockerfileUpload: "上传 Dockerfile",
|
|
customNodeCommand: "启动命令(每行一个参数)",
|
|
customNodeInputMode: "输入契约",
|
|
customNodeOutputMode: "输出契约",
|
|
customNodeArtifactType: "产物类型",
|
|
customNodeSingleAssetSet: "单资产集",
|
|
customNodeMultiAssetSet: "多资产集",
|
|
customNodeReport: "仅报告",
|
|
customNodeAssetSet: "资产集",
|
|
customNodeAssetSetWithReport: "资产集加报告",
|
|
createStorageConnection: "创建存储连接",
|
|
storageProvider: "存储提供方",
|
|
bucket: "Bucket",
|
|
endpoint: "Endpoint",
|
|
region: "Region",
|
|
basePath: "基础路径",
|
|
rootPath: "根路径",
|
|
noStorageConnectionsYet: "还没有存储连接。",
|
|
datasetsTitle: "数据集",
|
|
datasetsDescription: "从源资产创建项目数据集,并绑定到一个存储连接。",
|
|
datasetName: "数据集名称",
|
|
datasetDescription: "数据集描述",
|
|
sourceAsset: "源资产",
|
|
sourceAssets: "源资产",
|
|
storageConnection: "存储连接",
|
|
storagePathLabel: "存储路径",
|
|
createDataset: "创建数据集",
|
|
noDatasetsYet: "还没有创建任何数据集。",
|
|
latestDatasetVersion: "最新数据集版本",
|
|
localPath: "本地路径",
|
|
registerLocalPath: "注册本地路径",
|
|
noAssetsYet: "还没有注册任何资产。",
|
|
type: "类型",
|
|
source: "来源",
|
|
detected: "识别结果",
|
|
pending: "待探测",
|
|
topLevelEntries: "顶层条目",
|
|
notAvailable: "暂无",
|
|
loadingAssetDetail: "正在加载资产详情...",
|
|
assetId: "资产 ID",
|
|
status: "状态",
|
|
sourcePath: "源路径",
|
|
probeAgain: "重新探测",
|
|
createExploreArtifact: "创建查看产物",
|
|
openExploreView: "打开查看页",
|
|
probeSummary: "探测摘要",
|
|
warnings: "告警",
|
|
none: "无",
|
|
recommendedNodes: "推荐节点",
|
|
noProbeReportYet: "还没有探测报告。",
|
|
workflowsTitle: "工作流",
|
|
workflowTemplatesTitle: "工作流模板",
|
|
workflowTemplatesDescription: "从可复用模板创建工作流,或者直接在项目里创建空白工作流。",
|
|
createWorkflow: "新建工作流",
|
|
createBlankWorkflow: "创建空白工作流",
|
|
createWorkflowFromTemplate: "从模板创建工作流",
|
|
saveAsTemplate: "另存为模板",
|
|
templateName: "模板名称",
|
|
templateDescription: "模板描述",
|
|
templateSaved: "已保存模板",
|
|
noWorkflowTemplatesYet: "还没有工作流模板。",
|
|
noWorkflowsYet: "还没有工作流。",
|
|
latestVersion: "最新版本",
|
|
workflowEditor: "工作流编辑器",
|
|
runAsset: "运行资产",
|
|
saveWorkflowVersion: "保存工作流版本",
|
|
triggerWorkflowRun: "触发工作流运行",
|
|
reloadLatestSaved: "重新加载最新保存版本",
|
|
openLatestRun: "打开最新运行",
|
|
selectAssetBeforeRun: "触发工作流运行前请先选择资产。",
|
|
nodeLibrary: "节点面板",
|
|
nodeLibraryHint: "支持点击追加,也支持将节点拖入画布指定位置。",
|
|
canvas: "画布",
|
|
canvasHint: "支持自由拖动节点、拖拽连线、缩放和平移。",
|
|
dragNodeToCanvas: "将节点拖放到这里即可在画布中创建。",
|
|
latestSavedVersions: "最近保存版本",
|
|
draftStatus: "草稿状态",
|
|
draftSynced: "已同步",
|
|
draftUnsaved: "有未保存修改",
|
|
nodeConfiguration: "节点配置",
|
|
category: "分类",
|
|
definition: "定义",
|
|
executorType: "执行器类型",
|
|
runtimeTarget: "运行目标",
|
|
artifactTitle: "产物标题",
|
|
pythonCodeHook: "Python 代码钩子",
|
|
nodeNoHook: "该节点在 V1 中不开放代码钩子。",
|
|
selectNode: "请选择一个节点。",
|
|
removeNode: "删除节点",
|
|
workflowCreatedName: "交付标准化 {count}",
|
|
noAssetsAvailable: "没有可用资产",
|
|
runsTitle: "运行记录",
|
|
runsDescription: "当前项目最近的工作流执行记录。",
|
|
noRunsYet: "还没有工作流运行记录。",
|
|
createdAt: "创建时间",
|
|
inputAssets: "输入资产",
|
|
runDetail: "运行详情",
|
|
workflow: "工作流",
|
|
startedAt: "开始时间",
|
|
finishedAt: "结束时间",
|
|
runDuration: "运行时长",
|
|
runSummary: "运行摘要",
|
|
cancelRun: "取消运行",
|
|
retryRun: "重试运行",
|
|
runGraph: "运行图",
|
|
boundAssets: "绑定资产",
|
|
selectedTask: "当前任务",
|
|
executor: "执行器",
|
|
executorConfig: "执行器配置",
|
|
codeHook: "代码钩子",
|
|
duration: "耗时",
|
|
summary: "摘要",
|
|
error: "错误",
|
|
retryTask: "重试任务",
|
|
artifacts: "产物",
|
|
noTaskArtifactsYet: "当前任务还没有产物。",
|
|
stdout: "标准输出",
|
|
stderr: "标准错误",
|
|
executionLog: "执行日志",
|
|
resultPreview: "结果预览",
|
|
noStdout: "没有标准输出。",
|
|
noStderr: "没有标准错误。",
|
|
noTaskLogs: "当前任务还没有日志。",
|
|
noTasksCreated: "还没有生成任务。",
|
|
loadingRun: "正在加载运行...",
|
|
exploreTitle: "查看",
|
|
exploreEmpty: "先在资产详情或运行详情里创建产物,再来这里查看。",
|
|
loadingArtifact: "正在加载产物...",
|
|
bootstrappingLocalWorkspace: "正在初始化本地工作空间...",
|
|
failedLoadAssets: "加载资产失败",
|
|
failedLoadStorageConnections: "加载存储连接失败",
|
|
failedCreateStorageConnection: "创建存储连接失败",
|
|
failedLoadDatasets: "加载数据集失败",
|
|
failedCreateDataset: "创建数据集失败",
|
|
failedLoadCustomNodes: "加载自定义节点失败",
|
|
failedCreateCustomNode: "创建自定义节点失败",
|
|
failedRegisterAsset: "注册本地资产失败",
|
|
failedLoadWorkflows: "加载工作流失败",
|
|
failedLoadTemplates: "加载工作流模板失败",
|
|
failedCreateTemplate: "创建工作流模板失败",
|
|
failedCreateWorkflowFromTemplate: "从模板创建工作流失败",
|
|
failedLoadWorkflow: "加载工作流失败",
|
|
failedLoadRuns: "加载运行列表失败",
|
|
failedLoadRunDetail: "加载运行详情失败",
|
|
failedLoadTaskArtifacts: "加载任务产物失败",
|
|
failedCancelRun: "取消运行失败",
|
|
failedRetryRun: "重试运行失败",
|
|
failedRetryTask: "重试任务失败",
|
|
failedLoadArtifact: "加载产物失败",
|
|
failedBootstrap: "初始化本地上下文失败",
|
|
failedLoadProjects: "加载项目失败",
|
|
failedCreateProject: "创建项目失败",
|
|
validatedAssetCount: "已校验 {count} 个资产",
|
|
loadedAssetCount: "已加载 {count} 个绑定资产",
|
|
success: "成功",
|
|
failed: "失败",
|
|
running: "运行中",
|
|
queued: "排队中",
|
|
cancelled: "已取消",
|
|
totalTasks: "共 {count} 个任务",
|
|
stdoutLines: "{count} 条标准输出",
|
|
stderrLines: "{count} 条标准错误",
|
|
successCount: "{count} 个成功",
|
|
failedCount: "{count} 个失败",
|
|
runningCount: "{count} 个运行中",
|
|
cancelledCount: "{count} 个已取消",
|
|
artifactsCount: "产物 {count}",
|
|
viaExecutor: "{outcome},执行器 {executor}",
|
|
assetCount: "资产 {count}",
|
|
artifactCount: "产物 {count}",
|
|
invalidConnectionMissingEndpoint: "该连线缺少有效的起点或终点节点。",
|
|
invalidConnectionSelf: "节点不能连接自己。",
|
|
invalidConnectionDuplicate: "这条连线已经存在。",
|
|
invalidConnectionSourceDisallowsOutgoing: "V1 中导出节点不允许继续向外连线。",
|
|
invalidConnectionTargetDisallowsIncoming: "数据源节点不允许接收入边。",
|
|
invalidConnectionTargetAlreadyHasIncoming: "V1 中该节点只能保留一条上游入边。",
|
|
invalidConnectionCycle: "这条连线会形成环路。",
|
|
},
|
|
};
|
|
|
|
const BUILTIN_NODE_TRANSLATIONS: Record<string, { en: { name: string; description: string }; zh: { name: string; description: string } }> = {
|
|
"source-asset": {
|
|
en: { name: "Source Asset", description: "Load an uploaded asset or registered storage path." },
|
|
zh: { name: "数据源", description: "加载上传资产或已注册的存储路径。" },
|
|
},
|
|
"extract-archive": {
|
|
en: { name: "Extract Archive", description: "Expand a compressed archive into a managed folder artifact." },
|
|
zh: { name: "解压归档", description: "将压缩包展开成受管目录产物。" },
|
|
},
|
|
"rename-folder": {
|
|
en: { name: "Rename Delivery Folder", description: "Rename the top-level delivery folder to the business naming convention." },
|
|
zh: { name: "重命名交付目录", description: "将顶层交付目录重命名为业务命名规范。" },
|
|
},
|
|
"validate-structure": {
|
|
en: { name: "Validate Structure", description: "Validate required directories and metadata files." },
|
|
zh: { name: "校验目录结构", description: "校验必需目录和元数据文件。" },
|
|
},
|
|
"validate-metadata": {
|
|
en: { name: "Validate Metadata", description: "Validate meta.json, intrinsics.json, and video_meta.json." },
|
|
zh: { name: "校验元数据", description: "校验 meta.json、intrinsics.json 和 video_meta.json。" },
|
|
},
|
|
"union-assets": {
|
|
en: { name: "Union Assets", description: "Merge multiple upstream asset sets into one deduplicated asset set." },
|
|
zh: { name: "资产并集", description: "将多个上游资产集合合并为一个去重后的资产集合。" },
|
|
},
|
|
"intersect-assets": {
|
|
en: { name: "Intersect Assets", description: "Keep only the assets that exist in every upstream asset set." },
|
|
zh: { name: "资产交集", description: "只保留所有上游资产集合共同包含的资产。" },
|
|
},
|
|
"difference-assets": {
|
|
en: { name: "Difference Assets", description: "Subtract downstream asset sets from the first upstream asset set." },
|
|
zh: { name: "资产差集", description: "从第一个上游资产集合中减去后续上游资产集合。" },
|
|
},
|
|
"export-delivery-package": {
|
|
en: { name: "Export Delivery Package", description: "Produce the final delivery package artifact for upload." },
|
|
zh: { name: "导出交付包", description: "生成最终交付包产物用于上传或交付。" },
|
|
},
|
|
};
|
|
|
|
type InterpolationValues = Record<string, string | number>;
|
|
|
|
function interpolate(template: string, values?: InterpolationValues) {
|
|
if (!values) {
|
|
return template;
|
|
}
|
|
return template.replace(/\{(\w+)\}/gu, (_match, key) => String(values[key] ?? ""));
|
|
}
|
|
|
|
export function translate(
|
|
language: Language,
|
|
key: TranslationKey,
|
|
values?: InterpolationValues,
|
|
) {
|
|
return interpolate(TRANSLATIONS[language][key], values);
|
|
}
|
|
|
|
export function getInitialLanguage(): Language {
|
|
if (typeof window === "undefined") {
|
|
return "zh";
|
|
}
|
|
const stored = window.localStorage.getItem("emboflow-language");
|
|
if (stored === "en" || stored === "zh") {
|
|
return stored;
|
|
}
|
|
return window.navigator.language.toLowerCase().startsWith("zh") ? "zh" : "en";
|
|
}
|
|
|
|
export function localizeNodeDefinition<T extends { id: string; name?: string; description?: string; category?: string }>(
|
|
language: Language,
|
|
definition: T,
|
|
) {
|
|
const localized = BUILTIN_NODE_TRANSLATIONS[definition.id];
|
|
const categoryMap = {
|
|
Source: language === "zh" ? "数据源" : "Source",
|
|
Transform: language === "zh" ? "处理" : "Transform",
|
|
Inspect: language === "zh" ? "检查" : "Inspect",
|
|
Annotate: language === "zh" ? "标注" : "Annotate",
|
|
Export: language === "zh" ? "导出" : "Export",
|
|
Utility: language === "zh" ? "工具" : "Utility",
|
|
} as const;
|
|
return {
|
|
...definition,
|
|
name: localized ? localized[language].name : definition.name,
|
|
description: localized ? localized[language].description : definition.description,
|
|
category:
|
|
definition.category && definition.category in categoryMap
|
|
? categoryMap[definition.category as keyof typeof categoryMap]
|
|
: definition.category,
|
|
};
|
|
}
|
|
|
|
type I18nContextValue = {
|
|
language: Language;
|
|
setLanguage: (language: Language) => void;
|
|
t: (key: TranslationKey, values?: InterpolationValues) => string;
|
|
};
|
|
|
|
const I18nContext = createContext<I18nContextValue | null>(null);
|
|
|
|
export function I18nProvider(props: React.PropsWithChildren) {
|
|
const [language, setLanguage] = useState<Language>(() => getInitialLanguage());
|
|
|
|
useEffect(() => {
|
|
if (typeof document !== "undefined") {
|
|
document.documentElement.lang = language === "zh" ? "zh-CN" : "en";
|
|
}
|
|
if (typeof window !== "undefined") {
|
|
window.localStorage.setItem("emboflow-language", language);
|
|
}
|
|
}, [language]);
|
|
|
|
const value = useMemo<I18nContextValue>(
|
|
() => ({
|
|
language,
|
|
setLanguage,
|
|
t: (key, values) => translate(language, key, values),
|
|
}),
|
|
[language],
|
|
);
|
|
|
|
return <I18nContext.Provider value={value}>{props.children}</I18nContext.Provider>;
|
|
}
|
|
|
|
export function useI18n() {
|
|
const context = useContext(I18nContext);
|
|
if (!context) {
|
|
throw new Error("useI18n must be used within I18nProvider");
|
|
}
|
|
return context;
|
|
}
|