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> = { 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 = { "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; 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( 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(null); export function I18nProvider(props: React.PropsWithChildren) { const [language, setLanguage] = useState(() => 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( () => ({ language, setLanguage, t: (key, values) => translate(language, key, values), }), [language], ); return {props.children}; } export function useI18n() { const context = useContext(I18nContext); if (!context) { throw new Error("useI18n must be used within I18nProvider"); } return context; }