"use client";

import { createContext, useContext, useEffect, useMemo, useState } from "react";

export type Language = "en" | "zh-CN" | "zh-TW" | "ja";

type Dictionary = typeof en;
export type TranslationKey = keyof Dictionary;

const STORAGE_KEY = "popiart-node-language";

export const LANGUAGE_OPTIONS: { value: Language; label: string }[] = [
  { value: "en", label: "English" },
  { value: "zh-CN", label: "简体中文" },
  { value: "zh-TW", label: "繁體中文" },
  { value: "ja", label: "日本語" },
];

const en = {
  "language.label": "Language",
  "brand.name": "popiart-node",
  "common.back": "Back",
  "common.next": "Next",
  "common.cancel": "Cancel",
  "common.close": "Close",
  "common.save": "Save",
  "common.create": "Create",
  "common.browse": "Browse",
  "common.change": "Change",
  "common.clear": "Clear",
  "common.select": "Select",
  "common.selectModel": "Select Model",
  "common.skip": "Skip",
  "common.show": "Show",
  "common.hide": "Hide",
  "common.override": "Override",
  "common.configuredViaEnv": "Configured via .env",
  "common.recommended": "Recommended",
  "common.optional": "Optional",
  "common.required": "Required",
  "common.download": "Download",
  "common.remove": "Remove",
  "common.disconnect": "Disconnect",
  "common.play": "Play",
  "common.pause": "Pause",
  "common.set": "Set",
  "common.previous": "Previous",
  "common.nextItem": "Next",
  "common.processing": "Processing...",
  "error.providerOverloaded": "{model}: provider is temporarily overloaded. Please retry in a moment or switch models.",
  "node.optionalTitle": "This input is optional - empty inputs will be skipped",
  "node.markOptionalTitle": "Mark as optional - empty inputs will skip this branch",
  "header.openWelcome": "Open welcome screen",
  "header.untitled": "Untitled",
  "header.saving": "Saving...",
  "header.savedAt": "Saved {time}",
  "header.notSaved": "Not saved",
  "header.saveProject": "Save project",
  "header.configureSaveLocation": "Configure save location",
  "header.openProject": "Open project",
  "header.openProjectFolder": "Open Project Folder",
  "header.projectSettings": "Project settings",
  "header.revertAIChanges": "Revert AI Changes",
  "header.revertAIChangesTitle": "Restore workflow from before AI changes",
  "header.revertConfirm": "Are you sure? This will restore your previous workflow.",
  "header.shortcuts": "Keyboard shortcuts (?)",
  "header.support": "Support",
  "header.madeBy": "Made by Willie",
  "header.commentsTitle": "{unviewed} unviewed comments ({total} total)",
  "header.openFolderFailed": "Failed to open project folder: {error}",
  "header.openFolderFailedRetry": "Failed to open project folder. Please try again.",
  "header.saveFailedRetry": "Failed to save project. Please try again.",
  "quickstart.description": "A node based workflow editor for generative AI pipelines. Connect nodes to build pipelines that transform and generate images, video, audio and 3D assets.",
  "quickstart.docs": "Docs",
  "quickstart.discord": "Discord",
  "quickstart.waitlist": "NB Pro Waitlist",
  "quickstart.newProject": "New project",
  "quickstart.newProjectDesc": "Start a new workflow",
  "quickstart.loadWorkflow": "Load workflow",
  "quickstart.loadWorkflowDesc": "Open existing file",
  "quickstart.templates": "Templates",
  "quickstart.templatesDesc": "Pre-built workflows",
  "quickstart.promptWorkflow": "Prompt a workflow",
  "quickstart.promptWorkflowDesc": "Get Gemini to build it",
  "quickstart.beta": "Beta",
  "quickstart.templateExplorerTitle": "Template Explorer",
  "quickstart.workflowTemplatesTitle": "Workflow Templates",
  "quickstart.templatesIntro": "Pre-built workflows to help you get started quickly. Select a template to load it into the canvas.",
  "quickstart.quickStart": "Quick Start",
  "quickstart.searchTemplates": "Search templates",
  "quickstart.searchTemplatesPlaceholder": "Search templates...",
  "quickstart.category": "Category",
  "quickstart.provider": "Provider",
  "quickstart.filterAll": "All",
  "quickstart.categorySimple": "Simple",
  "quickstart.categoryAdvanced": "Advanced",
  "quickstart.clearFilters": "Clear filters",
  "quickstart.clearAllFilters": "Clear all filters",
  "quickstart.noTemplatesMatch": "No templates match your filters",
  "quickstart.adjustSearchFilters": "Try adjusting your search or filters",
  "quickstart.useWorkflow": "Use workflow",
  "quickstart.loading": "Loading...",
  "quickstart.nodeCount": "{count} nodes",
  "quickstart.dismiss": "Dismiss",
  "quickstart.loadTemplateFailed": "Failed to load template",
  "quickstart.template.productShot.name": "Product Shot",
  "quickstart.template.productShot.description": "Place product in a new scene or environment",
  "quickstart.template.modelProduct.name": "Model + Product",
  "quickstart.template.modelProduct.description": "Combine model, product, and scene",
  "quickstart.template.colorVariations.name": "Color Variations",
  "quickstart.template.colorVariations.description": "Generate product color variants from references",
  "quickstart.template.backgroundSwap.name": "Background Swap",
  "quickstart.template.backgroundSwap.description": "Place subject in a new background",
  "quickstart.template.styleTransfer.name": "Style Transfer",
  "quickstart.template.styleTransfer.description": "Apply style from one image to another",
  "quickstart.template.sceneComposite.name": "Scene Composite",
  "quickstart.template.sceneComposite.description": "Combine elements from multiple scenes",
  "ftux.welcomeTo": "Welcome to popiart-node",
  "ftux.stepWelcome": "Welcome",
  "ftux.stepApiKeys": "API Keys",
  "ftux.stepModelDefaults": "Model Defaults",
  "ftux.stepReady": "Ready",
  "ftux.getStarted": "Get Started",
  "ftux.skipSetup": "Skip setup?",
  "ftux.skipSetupDesc": "You can configure API keys and model defaults later in settings.",
  "ftux.welcomeTitle": "Let's get started.",
  "ftux.welcomeDesc": "Connect AI models like building blocks to generate images, videos, and more.",
  "ftux.apiKeysTitle": "API Keys",
  "ftux.apiKeysDesc": "Add keys here to use AI providers (stored in browser), or save them to your .env file for better security and persistence.",
  "ftux.getApiKey": "Get {provider} API key",
  "ftux.getApiKeyTip": "Get API key",
  "ftux.llmOnlyTip": "Used for LLM nodes only",
  "ftux.enterKey": "Enter key...",
  "ftux.modelsTitle": "Choose Your Models",
  "ftux.modelsDesc": "Pick your default AI models for images and videos. You can change these later.",
  "ftux.defaultImageModel": "Default Image Model",
  "ftux.defaultVideoModel": "Default Video Model",
  "ftux.noneSet": "None set",
  "ftux.readyTitle": "You're ready!",
  "ftux.readyDesc": "Want a quick tutorial?",
  "ftux.startTutorial": "Start tutorial",
  "ftux.tutorial.welcome": "Let's go over the basics.",
  "ftux.tutorial.addImage": "Click the Image button to add an image node.",
  "ftux.tutorial.explainNode": "This is a node. Each node has a specific function.\n\nThis node adds images.",
  "ftux.tutorial.explainNodeInputs": "Inputs always go in on the left side of the node.",
  "ftux.tutorial.explainNodeOutputs": "Outputs always come from the right side of the node.",
  "ftux.tutorial.dragAndDrop": "Now drag from the output handle and drop into empty space.",
  "ftux.tutorial.selectGenerateImage": "This menu will show all available connections for the node.\n\nSelect 'Generate Image' to add an AI image generation node.",
  "ftux.tutorial.addPromptNode": "Click the Prompt button to add a prompt node.",
  "ftux.tutorial.connectPromptToGenerate": "Drag from the Prompt's output handle to the Generate Image's input handle.",
  "ftux.tutorial.populateContent": "Let me just add some stuff here 🎨",
  "ftux.tutorial.explainGenerateNode": "This is the Generate Image node. It uses AI to create or modify images based on your prompt and reference image.",
  "ftux.tutorial.explainRunButton": "Clicking this Run button will run your workflow. You can also press Cmd+Enter (Ctrl+Enter on Windows).",
  "ftux.tutorial.explainRunOptions": "You can also click the dropdown to run from a specific node, or run only selected nodes.",
  "ftux.tutorial.runWorkflow": "Now let's run your workflow! Click the Run button to generate your image.",
  "ftux.tutorial.demonstrateDownstream": "Now let me show you the possibilities... 🎬✨",
  "ftux.tutorial.demonstrateComplete": "Connect more nodes downstream to build generative pipelines, or just use it as an infinite creative canvas.",
  "ftux.tutorial.saveProject": "Save your project to keep all your work and generations locally.",
  "ftux.tutorial.resources": "Check out the resources below for help and inspiration:",
  "ftux.tutorial.discord": "Join our Discord community",
  "ftux.tutorial.complete": "You're all set! Happy creating.",
  "ftux.tutorial.clickToContinue": "Click anywhere to continue",
  "ftux.tutorial.skip": "Skip tutorial",
  "toolbar.categoryInput": "Input",
  "toolbar.categoryText": "Text",
  "toolbar.categoryGenerate": "Generate",
  "toolbar.categoryProcess": "Process",
  "toolbar.categoryRoute": "Route",
  "toolbar.categoryOutput": "Output",
  "toolbar.image": "Image",
  "toolbar.video": "Video",
  "toolbar.prompt": "Prompt",
  "toolbar.generate": "Generate",
  "toolbar.output": "Output",
  "toolbar.allNodes": "All nodes",
  "toolbar.allModels": "All models",
  "toolbar.browseModels": "Browse Models",
  "toolbar.switchConnectors": "Switch to {style} connectors",
  "toolbar.curved": "curved",
  "toolbar.angular": "angular",
  "toolbar.run": "Run",
  "toolbar.stop": "Stop",
  "toolbar.running": "Running...",
  "toolbar.runningNode": "Running {node}...",
  "toolbar.runningNodes": "Running {count} nodes...",
  "toolbar.nodesCount": "{count} nodes",
  "toolbar.runOptions": "Run options",
  "toolbar.runEntire": "Run entire workflow",
  "toolbar.runFromSelected": "Run from selected node",
  "toolbar.runSelectedOnly": "Run selected node only",
  "toolbar.runSelectedNodes": "Run selected nodes",
  "toolbar.runSelectedNodesCount": "Run {count} selected nodes",
  "toolbar.selectSingleNode": "Select a single node first",
  "toolbar.selectOneOrMoreNodes": "Select one or more nodes first",
  "node.imageInput": "Image Input",
  "node.audioInput": "Audio Input",
  "node.videoInput": "Video Input",
  "node.glbViewer": "3D Viewer",
  "node.prompt": "Prompt",
  "node.promptConstructor": "Prompt Constructor",
  "node.array": "Array",
  "node.generateImage": "Generate Image",
  "node.generateVideo": "Generate Video",
  "node.generate3d": "Generate 3D",
  "node.generateAudio": "Generate Audio",
  "node.llmGenerate": "LLM Generate",
  "node.annotation": "Annotate",
  "annotation.tool.select": "Select",
  "annotation.tool.rectangle": "Rect",
  "annotation.tool.circle": "Circle",
  "annotation.tool.arrow": "Arrow",
  "annotation.tool.draw": "Draw",
  "annotation.tool.text": "Text",
  "annotation.undo": "Undo",
  "annotation.redo": "Redo",
  "annotation.clear": "Clear",
  "annotation.cancel": "Cancel",
  "annotation.done": "Done",
  "annotation.color": "Color",
  "annotation.size": "Size",
  "annotation.fill": "Fill",
  "node.splitGrid": "Split Grid",
  "node.videoStitch": "Video Stitch",
  "node.videoTrim": "Video Trim",
  "node.easeCurve": "Ease Curve",
  "node.frameGrab": "Frame Grab",
  "node.imageCompare": "Image Compare",
  "imageCompare.connectTwo": "Connect 2 images to compare",
  "imageCompare.connectAnother": "Connect another image to compare",
  "imageCompare.imageAConnected": "Image A connected",
  "imageCompare.imageBConnected": "Image B connected",
  "imageCompare.imageAAlt": "Image A",
  "imageCompare.imageBAlt": "Image B",
  "upload.imageUnsupported": "Unsupported format. Use PNG, JPG, or WebP.",
  "upload.imageTooLarge": "Image too large. Maximum size is 10MB.",
  "upload.videoUnsupported": "Unsupported format. Use MP4, WebM, or QuickTime video files.",
  "upload.videoTooLarge": "Video file too large. Maximum size is 200MB.",
  "upload.audioUnsupported": "Unsupported format. Use MP3, WAV, OGG, AAC, or other audio formats.",
  "upload.audioTooLarge": "Audio file too large. Maximum size is 50MB.",
  "imageInput.uploadedAlt": "Uploaded image",
  "imageInput.download": "Download image",
  "imageInput.remove": "Remove image",
  "videoInput.upload": "Upload video file",
  "videoInput.dropVideo": "Drop video or click",
  "videoInput.download": "Download video",
  "videoInput.remove": "Remove video",
  "audioInput.upload": "Upload audio file",
  "audioInput.dropAudio": "Drop audio or click",
  "audioInput.download": "Download audio",
  "audioInput.remove": "Remove audio",
  "audioInput.clear": "Clear audio",
  "audioInput.loadingWaveform": "Loading waveform...",
  "generateAudio.fallbackUsed": "Fallback used",
  "generateAudio.fallbackUsedTitle": "Primary failed: {error}\nUsed fallback: {model}",
  "generateAudio.generating": "Generating audio...",
  "annotation.noImage": "No image available. Connect an image or load one manually.",
  "annotation.annotatedAlt": "Annotated image",
  "annotation.downloadImage": "Download image",
  "annotation.removeImage": "Remove image",
  "annotation.editCount": "Edit ({count})",
  "annotation.addAnnotations": "Add annotations",
  "annotation.dropClickConnect": "Drop, click, or connect",
  "array.split": "Split",
  "array.delimiter": "Delimiter",
  "array.newline": "Newline",
  "array.regexAdvanced": "Regex (Advanced)",
  "array.batch": "Batch",
  "array.batchModeTitle": "Batch mode: all items sent to one downstream node",
  "array.enableBatchMode": "Enable batch mode",
  "array.autoRouteToPrompts": "Auto-route to Prompts",
  "array.by": "By",
  "array.advanced": "Advanced",
  "array.trim": "Trim",
  "array.removeEmpty": "Remove empty",
  "array.parsedItems": "Parsed Items ({count})",
  "array.noItemsParsed": "No items parsed",
  "array.selectedForNextConnection": "Selected for next connection (click to unselect)",
  "array.selectForNextConnection": "Click to select for next connection",
  "array.batchFooter": "Batch: all items sent to downstream node",
  "array.nextWireUsesItem": "Next wire uses item {index}",
  "array.noSelection": "No selection: wires advance in order from item 1",
  "videoCommon.encoderUnsupported": "Your browser doesn't support video encoding.",
  "videoCommon.encoderHelp": "Doesn't seem right? Message Willie on Discord.",
  "videoCommon.checkingEncoder": "Checking encoder...",
  "videoCommon.processingPercent": "Processing... {progress}%",
  "videoTrim.videoIn": "Video In",
  "videoTrim.videoOut": "Video Out",
  "videoTrim.connectVideo": "Connect a video to trim",
  "videoTrim.source": "Source",
  "videoTrim.trimmed": "Trimmed",
  "videoTrim.clearTrimmedVideo": "Clear trimmed video",
  "videoTrim.start": "Start",
  "videoTrim.end": "End",
  "videoTrim.duration": "Duration",
  "videoTrim.trimStartTime": "Trim start time",
  "videoTrim.trimEndTime": "Trim end time",
  "videoTrim.setStartToCurrent": "Set start to current playback time",
  "videoTrim.setEndToCurrent": "Set end to current playback time",
  "videoTrim.trim": "Trim",
  "videoStitch.videoInput": "Video {index}",
  "videoStitch.audioInput": "Audio",
  "videoStitch.output": "Output",
  "videoStitch.connectVideos": "Connect videos to stitch",
  "videoStitch.clipAlt": "Clip {id}",
  "videoStitch.clearVideo": "Clear video",
  "videoStitch.loop": "Loop",
  "videoStitch.stitch": "Stitch",
  "multiAngle.editorTitle": "Multi-Angle Editor",
  "multiAngle.imageEditorTitle": "Image Multi-Angle Editor",
  "multiAngle.imageTitle": "Image Multi-Angle",
  "multiAngle.closeEditor": "Close multi-angle editor",
  "multiAngle.generate": "Generate multi-angle",
  "multiAngle.noImageToast": "No usable image found. Cannot generate multi-angle image.",
  "multiAngle.sightControl": "Multi-angle sight control",
  "multiAngle.noImage": "No image",
  "multiAngle.horizontal": "Horizontal orbit",
  "multiAngle.vertical": "Vertical pitch",
  "multiAngle.zoom": "Shot scale",
  "multiAngle.prompt": "Prompt",
  "multiAngle.enableCustomPrompt": "Enable custom prompt",
  "multiAngle.promptDialog": "Multi-angle prompt",
  "multiAngle.promptInput": "Multi-angle prompt input",
  "multiAngle.promptPlaceholder": "Enter prompt...",
  "multiAngle.reset": "Reset parameters",
  "multiAngle.resetAria": "Reset multi-angle parameters",
  "multiAngle.adjustVerticalUp": "Adjust vertical pitch up",
  "multiAngle.adjustVerticalDown": "Adjust vertical pitch down",
  "multiAngle.adjustHorizontalLeft": "Adjust horizontal orbit left",
  "multiAngle.adjustHorizontalRight": "Adjust horizontal orbit right",
  "multiAngle.preset.custom": "Custom",
  "multiAngle.preset.fisheye": "Fisheye",
  "multiAngle.preset.tilt": "Tilt",
  "multiAngle.preset.frontOverhead": "Front overhead",
  "multiAngle.preset.frontLow": "Front low angle",
  "multiAngle.preset.panoramicOverhead": "Panoramic overhead",
  "multiAngle.preset.back": "Back view",
  "multiAngle.zoom.wide": "Wide",
  "multiAngle.zoom.medium": "Medium",
  "multiAngle.zoom.close": "Close-up",
  "workflow.splitGrid.noImage": "No image available to split. Generate or load an image first.",
  "workflow.splitGrid.detectFailed": "Could not detect grid in image.",
  "workflow.splitGrid.failed": "Failed to split image grid: {error}",
  "workflow.generatedSuccess": "Workflow generated successfully",
  "workflow.generatedFailed": "Failed to generate workflow",
  "workflow.generatedFailedRetry": "Failed to generate workflow. Please try again.",
  "workflow.appliedEdits": "Applied {count} edit(s)",
  "chat.title": "Canvas Assistant",
  "chat.beta": "Beta",
  "chat.clear": "Clear chat",
  "chat.close": "Close chat",
  "chat.dismiss": "Dismiss",
  "chat.emptyTitle": "Ask about this canvas or ask me to prepare edits.",
  "chat.diagnoseSelected": "Diagnose selected",
  "chat.makeVariants": "Make variants",
  "chat.promptDiagnose": "Diagnose the selected nodes and suggest the smallest useful fix.",
  "chat.promptVariants": "Prepare edits to create four visual variants from the selected image or generate node.",
  "chat.proposalReady": "Proposal ready",
  "chat.candidateReady": "Candidate workflow ready to apply.",
  "chat.runReady": "Run action ready to apply.",
  "chat.candidateWorkflow": "Candidate workflow",
  "chat.applyWorkflow": "Apply workflow",
  "chat.operationPreview": "Operation preview",
  "chat.applyEdits": "Apply edits",
  "chat.runPreview": "Run preview",
  "chat.runNow": "Run now",
  "chat.focusedNodes": "Focused on {count} selected node(s)",
  "chat.runSelected": "Run selected",
  "chat.runAffected": "Run affected ({count})",
  "chat.placeholder": "Type a message...",
  "chat.building": "Building...",
  "chat.buildWorkflow": "Build Workflow",
  "chat.open": "Assistant",
  "chat.noSelection": "Select one or more nodes first.",
  "workflow.invalidWorkflowFile": "Invalid workflow file format",
  "workflow.parseWorkflowFileFailed": "Failed to parse workflow file",
  "splitGridNode.sourceGridAlt": "Source grid",
  "splitGridNode.connectImage": "Connect image",
  "splitGridNode.gridSummary": "{rows}x{cols} grid ({count} images)",
  "splitGridNode.generateSetsCreated": "{count} generate sets created",
  "splitGridNode.notConfigured": "Not configured - click Settings",
  "splitGridNode.configureFirst": "Configure node first",
  "splitGridNode.connectImageFirst": "Connect an image first",
  "splitGridNode.splitGrid": "Split grid",
  "splitGridNode.split": "Split",
  "node.router": "Router",
  "node.switch": "Switch",
  "node.conditionalSwitch": "Conditional Switch",
  "node.output": "Output",
  "node.outputGallery": "Output Gallery",
  "output.downloadCurrent": "Download current {type}",
  "output.downloadAll": "Download all outputs",
  "node.settings": "Settings",
  "node.runToGenerate": "Run to generate",
  "node.selectModel": "Select model...",
  "node.primary": "Primary",
  "node.text": "Text",
  "node.image": "Image",
  "node.notUsedByModel": "Not used by this model",
  "node.generationFailed": "Generation failed",
  "node.videoGenerationFailed": "Video generation failed",
  "node.loadingParameters": "Loading parameters...",
  "node.noParameters": "No parameters available",
  "node.default": "Default",
  "node.model": "Model",
  "node.size": "Size",
  "node.quality": "Quality",
  "node.voice": "Voice",
  "node.sound": "Sound",
  "node.duration": "Duration",
  "node.seconds": "{count}s",
  "node.aspectRatio": "Aspect Ratio",
  "node.resolution": "Resolution",
  "node.googleSearch": "Google Search",
  "node.imageSearch": "Image Search",
  "node.provider": "Provider",
  "node.temperature": "Temperature: {value}",
  "node.maxTokens": "Max Tokens: {value}",
  "node.generic": "Node",
  "splitGridSettings.title": "Split Grid Settings",
  "splitGridSettings.gridLayout": "Grid Layout",
  "splitGridSettings.gridSummary": "Grid will be split into {rows}x{cols} = {count} images",
  "splitGridSettings.defaultPrompt": "Default Prompt",
  "splitGridSettings.promptPlaceholder": "Enter prompt that will be applied to all generated images...",
  "splitGridSettings.promptHelp": "Each prompt node can be edited individually after creation",
  "splitGridSettings.generateNodeSettings": "Generate Node Settings",
  "splitGridSettings.createGenerateSets": "Create {count} Generate Sets",
  "imageInput.upload": "Upload image",
  "imageInput.dropImage": "Drop image",
  "dropOverlay.workflow": "Drop to load workflow",
  "dropOverlay.node": "Drop to create node",
  "dropOverlay.audio": "Drop audio to create node",
  "dropOverlay.image": "Drop image to create node",
  "dropOverlay.splittingImageGrid": "Splitting image grid...",
  "connectionMenu.addNode": "Add {type} node",
  "connectionMenu.type.image": "image",
  "connectionMenu.type.text": "text",
  "connectionMenu.type.audio": "audio",
  "connectionMenu.type.video": "video",
  "connectionMenu.type.3d": "3D",
  "connectionMenu.type.easeCurve": "settings",
  "connectionMenu.splitGridNode": "Split Grid Node",
  "connectionMenu.splitGridNow": "Split Grid Now",
  "connectionMenu.multiAngleImage": "Image Multi-Angle",
  "connectionMenu.navigate": "navigate",
  "connectionMenu.select": "select",
  "prompt.placeholder": "Describe what to generate...",
  "prompt.connectedPlaceholder": "Text from connected node (editable)...",
  "prompt.optionalPlaceholder": "Optional prompt (leave empty to skip)...",
  "prompt.addVariable": "Add variable",
  "prompt.setVariableName": "Set Variable Name",
  "prompt.variableHelp": "Use this prompt as a variable in PromptConstructor nodes",
  "prompt.variableName": "Variable name",
  "prompt.variablePlaceholder": "e.g. color, style, subject",
  "prompt.preview": "Preview: {value}",
  "composer.promptProvidedByUpstream": "Prompt is provided by an upstream text node.",
  "composer.videoStitchClipCount": "{count} video clips",
  "composer.videoStitchAudio": "Audio connected",
  "composer.videoStitchNoAudio": "No audio",
  "composer.videoStitchInputs": "Video inputs",
  "composer.videoStitchInputHint": "Reorder clips on the node",
  "composer.videoStitchNeedTwo": "Connect at least 2 video clips to stitch.",
  "composer.videoStitchClip": "Clip {index}",
  "composer.videoStitchLoop": "Loop",
  "composer.videoStitchOutputReady": "Output ready",
  "composer.videoStitchNoOutput": "No output yet",
  "composer.videoStitchReady": "Ready to stitch current node",
  "composer.videoStitchRun": "Stitch current video node",
  "composer.easeCurveVideoConnected": "Video connected",
  "composer.easeCurveNoVideo": "Connect a video input to apply ease curve.",
  "composer.easeCurveInherited": "Inherited settings",
  "composer.easeCurveLocal": "Local settings",
  "composer.easeCurveSettings": "Curve settings",
  "composer.easeCurvePreset": "Preset",
  "composer.easeCurveCustom": "Custom bezier",
  "composer.easeCurveHandles": "Bezier handles",
  "composer.easeCurveInput": "Input",
  "composer.easeCurveDuration": "Output duration",
  "composer.easeCurveOutputReady": "Output ready",
  "composer.easeCurveNoOutput": "No output yet",
  "composer.easeCurveReady": "Ready to apply current curve",
  "composer.easeCurveRun": "Apply current ease curve node",
  "composer.selectModel": "Select model",
  "composer.chooseModelTitle": "Choose generation model",
  "composer.chooseImageModel": "Choose image model",
  "composer.chooseVideoModel": "Choose video model",
  "composer.chooseAudioModel": "Choose audio model",
  "composer.regenerateImageNode": "Regenerate current image node",
  "composer.regenerateVideoNode": "Regenerate current video node",
  "composer.regenerateAudioNode": "Regenerate current audio node",
  "composer.imageNode": "Image node",
  "composer.videoNode": "Video node",
  "composer.audioNode": "Audio node",
  "composer.imageCount": "Image count",
  "composer.imageCountOption": "{count} image(s)",
  "composer.newGeneration": "New generation",
  "composer.selectedNodes": "{count} nodes selected",
  "composer.currentNode": "Current node",
  "composer.createAndGenerate": "Create and generate",
  "composer.dragFromPort": "Drag from a node port to create the next step",
  "composer.multiSelectNoSingleGenerate": "Multi-select does not support single-node generation yet",
  "composer.retryCurrentNode": "Retry current node",
  "composer.generate": "Generate",
  "composer.describeContent": "Describe what you want to generate",
  "composer.mainPlaceholder": "Describe what you want to generate, press / for commands, @ to reference assets",
  "composer.uploadReferenceImage": "Upload reference image",
  "composer.expand": "Expand composer",
  "composer.collapse": "Collapse composer",
  "composer.console": "Console",
  "composer.style": "Style",
  "composer.mark": "Mark",
  "composer.focus": "Focus",
  "composer.addReferenceImage": "Add reference image",
  "composer.referenceFromUpstream": "Reference images come from upstream connections",
  "composer.reference": "Reference",
  "composer.referenceImage": "Reference image {index}",
  "composer.removeReferenceImage": "Remove reference image {index}",
  "composer.commands": "Commands",
  "composer.referenceMaterials": "Reference assets",
  "composer.commandCharacter": "Character image",
  "composer.commandCharacterValue": "Generate a character image with consistent styling and clear composition.",
  "composer.commandTurnaround": "Turnaround sheet",
  "composer.commandTurnaroundValue": "Generate a character turnaround sheet with front, side, and back views.",
  "composer.commandVideoShot": "Video shot",
  "composer.commandVideoShotValue": "Turn the current image into a short video shot with natural motion.",
  "composer.currentReference": "Current reference asset",
  "composer.canvasAssets": "Canvas assets",
  "composer.multiSelectUnsupported": "{count} nodes selected. Batch editing is not supported yet.",
  "composer.unsupportedNodeHint": "Create the next step by dragging from a node port. The composer only edits the selected generation node.",
  "composer.videoSchemaHint": "Video parameters follow the selected model schema",
  "composer.translate": "Translate",
  "composer.settings": "Settings",
  "composer.selectModelTitle": "Select model: {model}",
  "shortcuts.title": "Keyboard Shortcuts",
  "shortcuts.general": "General",
  "shortcuts.addNodes": "Add Nodes",
  "shortcuts.layout": "Layout (select 2+ nodes first)",
  "shortcuts.canvas": "Canvas",
  "shortcuts.runWorkflow": "Run workflow",
  "shortcuts.copyNodes": "Copy selected nodes",
  "shortcuts.paste": "Paste nodes / image / text",
  "shortcuts.undo": "Undo",
  "shortcuts.redo": "Redo",
  "shortcuts.showShortcuts": "Show keyboard shortcuts",
  "shortcuts.addPrompt": "Add Prompt node",
  "shortcuts.addImage": "Add Image Input node",
  "shortcuts.addGenerateImage": "Add Generate Image node",
  "shortcuts.addGenerateVideo": "Add Generate Video node",
  "shortcuts.addLLM": "Add LLM Text node",
  "shortcuts.addAnnotation": "Add Annotation node",
  "shortcuts.addAudio": "Add Audio node",
  "shortcuts.addVideoInput": "Add Video Input node",
  "shortcuts.addArray": "Add Array node",
  "shortcuts.stackVertical": "Stack selected vertically",
  "shortcuts.stackHorizontal": "Stack selected horizontally",
  "shortcuts.arrangeGrid": "Arrange selected as grid",
  "shortcuts.zoom": "Zoom in / out",
  "shortcuts.pan": "Pan (macOS)",
  "shortcuts.delete": "Delete selected nodes",
  "project.newProject": "New Project",
  "project.settings": "Project Settings",
  "project.tabProject": "Project",
  "project.tabProviders": "Providers",
  "project.tabNodeDefaults": "Node Defaults",
  "project.tabCanvas": "Canvas",
  "project.name": "Project Name",
  "project.directory": "Project Directory",
  "project.localFolderPlaceholder": "Choose a local folder on this device",
  "project.directoryHelp": "Workflow files and images will be saved here. Subfolders for inputs and generations will be auto-created.",
  "project.embedImages": "Embed images as base64",
  "project.embedImagesDesc": "Embeds all images in workflow, larger workflow files. Can hit memory limits on very large workflows.",
  "project.inlineSettings": "Show model settings on nodes",
  "project.inlineSettingsDesc": "Show model parameters inside generation nodes instead of the side panel",
  "project.envHelp": "Add API keys via .env.local for better security. Keys added here override .env and are stored in your browser.",
  "project.newapiwgDesc": "Popi unified model gateway",
  "project.baseUrl": "Base URL",
  "project.systemImageDefault": "System default (Popi Models)",
  "project.noneSetFirstUse": "None set (select on first use)",
  "project.defaultLLM": "Default LLM Settings",
  "project.systemLLMDefault": "Using system defaults (Popi Models Doubao Seed 2.0 Lite)",
  "project.provider": "Provider",
  "project.model": "Model",
  "project.temp": "Temp: {value}",
  "project.tokens": "Tokens: {value}",
  "project.executionSettings": "Execution Settings",
  "project.maxParallel": "Max Parallel Calls: {count}",
  "project.executionHelp": "Maximum number of nodes to execute in parallel during workflow execution. Higher values may improve speed but increase API rate limit risk.",
  "project.defaultsHelp": "These defaults are applied when creating nodes via keyboard shortcuts (Shift+G, Shift+L, etc).",
  "project.canvasHelp": "Configure how you navigate and interact with the canvas.",
  "project.canvasAssistant": "Canvas Assistant",
  "project.canvasAssistantShort": "Assistant",
  "project.panMode": "Pan Mode",
  "project.panSpace": "Hold Space and drag to pan",
  "project.panMiddle": "Click and drag with middle mouse button",
  "project.panAlways": "Pan without holding any keys",
  "project.panSpaceLabel": "Space + Drag",
  "project.panMiddleLabel": "Middle Mouse",
  "project.panAlwaysLabel": "Always On",
  "project.zoomMode": "Zoom Mode",
  "project.zoomAlt": "Hold Alt and scroll to zoom",
  "project.zoomCtrl": "Hold Ctrl/Cmd and scroll to zoom",
  "project.zoomScroll": "Scroll to zoom without modifier keys",
  "project.zoomAltLabel": "Alt + Scroll",
  "project.zoomCtrlLabel": "Ctrl + Scroll",
  "project.zoomScrollLabel": "Scroll",
  "project.selectionMode": "Selection Mode",
  "project.selectClick": "Click to select nodes",
  "project.selectAlt": "Hold Alt and drag to select",
  "project.selectShift": "Hold Shift and drag to select",
  "project.selectClickLabel": "Click",
  "project.selectAltLabel": "Alt + Drag",
  "project.selectShiftLabel": "Shift + Drag",
  "project.validating": "Validating...",
  "project.errorOpenDir": "Failed to open directory picker",
  "project.errorOpenDirWithReason": "Failed to open directory picker: {error}",
  "project.errorNameRequired": "Project name is required",
  "project.errorDirectoryRequired": "Project directory is required",
  "project.errorChooseLocalFolder": "Choose a local folder from this browser so the project is saved on this device.",
  "project.errorAbsolutePath": "Project directory must be an absolute path (starting with /, a drive letter, or a UNC path)",
  "project.errorNotDirectory": "Project path is not a directory",
  "project.errorValidateDir": "Failed to validate directory: {error}",
  "modelSearch.search": "Search models...",
  "modelSearch.allProviders": "All Providers",
  "modelSearch.all": "All",
  "modelSearch.allTypes": "All Types",
  "modelSearch.image": "Image",
  "modelSearch.video": "Video",
  "modelSearch.audio": "Audio",
  "modelSearch.refresh": "Refresh models & schemas",
  "modelSearch.loading": "Loading models...",
  "modelSearch.tryAgain": "Try Again",
  "modelSearch.removeFallback": "Remove fallback",
  "modelSearch.clearSelection": "Clear current selection",
  "modelSearch.noModels": "No models found",
  "modelSearch.adjustFilters": "Try adjusting your search or filters",
  "modelSearch.recent": "Recently Used",
  "promptWorkflow.title": "Prompt a Workflow",
  "promptWorkflow.describe": "Describe your workflow",
  "promptWorkflow.placeholder": "e.g., Create product photography with consistent lighting and style from reference images...",
  "promptWorkflow.help": "Describe what you want your workflow to accomplish. Be specific about inputs, outputs, and any transformations.",
  "promptWorkflow.note": "Note: This feature currently only works with Gemini models.",
  "promptWorkflow.generating": "Generating...",
  "promptWorkflow.generate": "Generate Workflow",
  "promptWorkflow.dismiss": "Dismiss",
  "promptWorkflow.validation": "Please describe your workflow (at least 3 characters)",
  "promptWorkflow.failed": "Failed to generate workflow",
  "workflowBrowser.noWorkflows": "No workflows found",
  "workflowBrowser.noWorkflowsDesc": "This folder doesn't contain any workflow projects",
  "workflowBrowser.title": "Your Workflows",
  "workflowBrowser.chooseFolderDesc": "Choose the folder that contains your workflow projects. You can change this later.",
  "workflowBrowser.chooseFolder": "Choose folder",
  "workflowBrowser.projectCount": "{count} projects",
  "workflowBrowser.openFromDirectory": "Open from directory",
  "workflowBrowser.changeFolder": "Change folder",
  "workflowBrowser.justNow": "just now",
  "workflowBrowser.minutesAgo": "{count}m ago",
  "workflowBrowser.hoursAgo": "{count}h ago",
  "workflowBrowser.daysAgo": "{count}d ago",
  "workflowBrowser.monthsAgo": "{count}mo ago",
  "workflowBrowser.errorNoWorkflows": "No workflows found in this folder",
  "workflowBrowser.errorList": "Failed to list workflows",
  "workflowBrowser.errorFetch": "Failed to fetch workflows",
  "multiSelect.stackHorizontal": "Stack horizontally (H)",
  "multiSelect.stackVertical": "Stack vertically (V)",
  "multiSelect.arrangeGrid": "Arrange as grid (G)",
  "multiSelect.removeGroup": "Remove from group",
  "multiSelect.createGroup": "Create group",
  "multiSelect.downloadZip": "Download images as ZIP",
  "cost.viewDetails": "View cost details",
};

const zhCN: Dictionary = {
  ...en,
  "language.label": "语言",
  "common.back": "返回",
  "common.next": "下一步",
  "common.cancel": "取消",
  "common.close": "关闭",
  "common.save": "保存",
  "common.create": "创建",
  "common.browse": "浏览",
  "common.change": "更改",
  "common.clear": "清除",
  "common.select": "选择",
  "common.selectModel": "选择模型",
  "common.skip": "跳过",
  "common.show": "显示",
  "common.hide": "隐藏",
  "common.override": "覆盖",
  "common.configuredViaEnv": "已通过 .env 配置",
  "common.recommended": "推荐",
  "common.optional": "可选",
  "common.required": "必填",
  "common.download": "下载",
  "common.remove": "移除",
  "common.disconnect": "断开连接",
  "common.play": "播放",
  "common.pause": "暂停",
  "common.set": "设置",
  "common.previous": "上一个",
  "common.nextItem": "下一个",
  "common.processing": "处理中...",
  "error.providerOverloaded": "{model}：服务商暂时过载。请稍后重试，或切换到其他模型。",
  "node.optionalTitle": "此输入为可选，空输入会被跳过",
  "node.markOptionalTitle": "标记为可选，空输入会跳过这个分支",
  "header.openWelcome": "打开欢迎页",
  "header.untitled": "未命名",
  "header.saving": "保存中...",
  "header.savedAt": "已保存 {time}",
  "header.notSaved": "未保存",
  "header.saveProject": "保存项目",
  "header.configureSaveLocation": "配置保存位置",
  "header.openProject": "打开项目",
  "header.openProjectFolder": "打开项目文件夹",
  "header.projectSettings": "项目设置",
  "header.revertAIChanges": "还原 AI 改动",
  "header.revertAIChangesTitle": "恢复到 AI 改动前的工作流",
  "header.revertConfirm": "确定吗？这会恢复到之前的工作流。",
  "header.shortcuts": "快捷键 (?)",
  "header.support": "支持",
  "header.commentsTitle": "{unviewed} 条未读评论（共 {total} 条）",
  "header.openFolderFailed": "打开项目文件夹失败：{error}",
  "header.openFolderFailedRetry": "打开项目文件夹失败，请重试。",
  "header.saveFailedRetry": "保存项目失败，请重试。",
  "quickstart.description": "一个面向生成式 AI 流水线的节点式工作流编辑器。连接节点即可构建生成与转换图片、视频、音频和 3D 资产的流程。",
  "quickstart.docs": "文档",
  "quickstart.waitlist": "NB Pro 候补名单",
  "quickstart.newProject": "新建项目",
  "quickstart.newProjectDesc": "开始新的工作流",
  "quickstart.loadWorkflow": "加载工作流",
  "quickstart.loadWorkflowDesc": "打开已有文件",
  "quickstart.templates": "模板",
  "quickstart.templatesDesc": "预置工作流",
  "quickstart.promptWorkflow": "用提示生成工作流",
  "quickstart.promptWorkflowDesc": "让 Gemini 帮你构建",
  "quickstart.beta": "测试版",
  "quickstart.templateExplorerTitle": "模板浏览器",
  "quickstart.workflowTemplatesTitle": "工作流模板",
  "quickstart.templatesIntro": "使用预置工作流快速开始。选择一个模板即可加载到画布。",
  "quickstart.quickStart": "快速开始",
  "quickstart.searchTemplates": "搜索模板",
  "quickstart.searchTemplatesPlaceholder": "搜索模板...",
  "quickstart.category": "分类",
  "quickstart.provider": "服务商",
  "quickstart.filterAll": "全部",
  "quickstart.categorySimple": "简单",
  "quickstart.categoryAdvanced": "高级",
  "quickstart.clearFilters": "清除筛选",
  "quickstart.clearAllFilters": "清除全部筛选",
  "quickstart.noTemplatesMatch": "没有匹配的模板",
  "quickstart.adjustSearchFilters": "尝试调整搜索或筛选条件",
  "quickstart.useWorkflow": "使用工作流",
  "quickstart.loading": "加载中...",
  "quickstart.nodeCount": "{count} 个节点",
  "quickstart.dismiss": "关闭",
  "quickstart.loadTemplateFailed": "加载模板失败",
  "quickstart.template.productShot.name": "产品图",
  "quickstart.template.productShot.description": "将产品放入新的场景或环境",
  "quickstart.template.modelProduct.name": "模特 + 产品",
  "quickstart.template.modelProduct.description": "组合模特、产品和场景",
  "quickstart.template.colorVariations.name": "颜色变体",
  "quickstart.template.colorVariations.description": "基于参考图生成产品配色变体",
  "quickstart.template.backgroundSwap.name": "替换背景",
  "quickstart.template.backgroundSwap.description": "将主体放入新的背景",
  "quickstart.template.styleTransfer.name": "风格迁移",
  "quickstart.template.styleTransfer.description": "将一张图的风格应用到另一张图",
  "quickstart.template.sceneComposite.name": "场景合成",
  "quickstart.template.sceneComposite.description": "组合多个场景中的元素",
  "ftux.welcomeTo": "欢迎使用 popiart-node",
  "ftux.stepWelcome": "欢迎",
  "ftux.stepApiKeys": "API 密钥",
  "ftux.stepModelDefaults": "模型默认值",
  "ftux.stepReady": "就绪",
  "ftux.getStarted": "开始使用",
  "ftux.skipSetup": "跳过设置？",
  "ftux.skipSetupDesc": "稍后可在设置中配置 API 密钥和模型默认值。",
  "ftux.welcomeTitle": "开始吧。",
  "ftux.welcomeDesc": "像搭积木一样连接 AI 模型，生成图片、视频等内容。",
  "ftux.apiKeysTitle": "API 密钥",
  "ftux.apiKeysDesc": "在这里添加密钥以使用 AI 服务商（存储在浏览器），也可以写入 .env 文件以获得更好的安全性和持久性。",
  "ftux.getApiKey": "获取 {provider} API 密钥",
  "ftux.getApiKeyTip": "获取 API 密钥",
  "ftux.llmOnlyTip": "仅用于 LLM 节点",
  "ftux.enterKey": "输入密钥...",
  "ftux.modelsTitle": "选择你的模型",
  "ftux.modelsDesc": "选择图片和视频的默认 AI 模型，之后可以随时更改。",
  "ftux.defaultImageModel": "默认图片模型",
  "ftux.defaultVideoModel": "默认视频模型",
  "ftux.noneSet": "未设置",
  "ftux.readyTitle": "准备好了！",
  "ftux.readyDesc": "要快速看一遍教程吗？",
  "ftux.startTutorial": "开始教程",
  "ftux.tutorial.welcome": "我们先快速了解基础操作。",
  "ftux.tutorial.addImage": "点击“图片”按钮，添加一个图片节点。",
  "ftux.tutorial.explainNode": "这是一个节点。每个节点都有特定功能。\n\n这个节点用于添加图片。",
  "ftux.tutorial.explainNodeInputs": "输入端口始终在节点左侧。",
  "ftux.tutorial.explainNodeOutputs": "输出端口始终在节点右侧。",
  "ftux.tutorial.dragAndDrop": "现在从输出端口拖拽，并释放到画布空白处。",
  "ftux.tutorial.selectGenerateImage": "这个菜单会显示该节点可连接的所有节点。\n\n选择“生成图片”，添加一个 AI 图片生成节点。",
  "ftux.tutorial.addPromptNode": "点击“提示词”按钮，添加一个提示词节点。",
  "ftux.tutorial.connectPromptToGenerate": "从提示词节点的输出端口拖到生成图片节点的输入端口。",
  "ftux.tutorial.populateContent": "我来帮你填充一些示例内容 🎨",
  "ftux.tutorial.explainGenerateNode": "这是生成图片节点。它会根据提示词和参考图，用 AI 创建或修改图片。",
  "ftux.tutorial.explainRunButton": "点击这个“运行”按钮会执行工作流。你也可以按 Cmd+Enter（Windows 上为 Ctrl+Enter）。",
  "ftux.tutorial.explainRunOptions": "你也可以点击下拉菜单，从指定节点开始运行，或只运行选中的节点。",
  "ftux.tutorial.runWorkflow": "现在运行你的工作流！点击“运行”按钮生成图片。",
  "ftux.tutorial.demonstrateDownstream": "现在看看还能继续做什么... 🎬✨",
  "ftux.tutorial.demonstrateComplete": "继续向下游连接更多节点来构建生成式流水线，也可以把它当作无限创意画布使用。",
  "ftux.tutorial.saveProject": "保存项目后，你的工作和生成结果都会保存在本地。",
  "ftux.tutorial.resources": "下面这些资源可以提供帮助和灵感：",
  "ftux.tutorial.discord": "加入我们的 Discord 社区",
  "ftux.tutorial.complete": "全部设置好了！开始创作吧。",
  "ftux.tutorial.clickToContinue": "点击任意位置继续",
  "ftux.tutorial.skip": "跳过教程",
  "toolbar.categoryInput": "输入",
  "toolbar.categoryText": "文本",
  "toolbar.categoryGenerate": "生成",
  "toolbar.categoryProcess": "处理",
  "toolbar.categoryRoute": "路由",
  "toolbar.categoryOutput": "输出",
  "toolbar.image": "图片",
  "toolbar.video": "视频",
  "toolbar.prompt": "提示词",
  "toolbar.generate": "生成",
  "toolbar.output": "输出",
  "toolbar.allNodes": "所有节点",
  "toolbar.allModels": "所有模型",
  "toolbar.browseModels": "浏览模型",
  "toolbar.switchConnectors": "切换为{style}连线",
  "toolbar.curved": "曲线",
  "toolbar.angular": "折线",
  "toolbar.run": "运行",
  "toolbar.stop": "停止",
  "toolbar.running": "运行中...",
  "toolbar.runningNode": "正在运行 {node}...",
  "toolbar.runningNodes": "正在运行 {count} 个节点...",
  "toolbar.nodesCount": "{count} 个节点",
  "toolbar.runOptions": "运行选项",
  "toolbar.runEntire": "运行整个工作流",
  "toolbar.runFromSelected": "从选中节点开始运行",
  "toolbar.runSelectedOnly": "仅运行选中节点",
  "toolbar.runSelectedNodes": "运行选中节点",
  "toolbar.runSelectedNodesCount": "运行 {count} 个选中节点",
  "toolbar.selectSingleNode": "请先选择一个节点",
  "toolbar.selectOneOrMoreNodes": "请先选择一个或多个节点",
  "node.imageInput": "图片输入",
  "node.audioInput": "音频输入",
  "node.videoInput": "视频输入",
  "node.glbViewer": "3D 查看器",
  "node.prompt": "提示词",
  "node.promptConstructor": "提示词构造器",
  "node.array": "数组",
  "node.generateImage": "生成图片",
  "node.generateVideo": "生成视频",
  "node.generate3d": "生成 3D",
  "node.generateAudio": "生成音频",
  "node.llmGenerate": "LLM 生成",
  "node.annotation": "标注",
  "annotation.tool.select": "选择",
  "annotation.tool.rectangle": "矩形",
  "annotation.tool.circle": "圆形",
  "annotation.tool.arrow": "箭头",
  "annotation.tool.draw": "画笔",
  "annotation.tool.text": "文字",
  "annotation.undo": "撤销",
  "annotation.redo": "重做",
  "annotation.clear": "清空",
  "annotation.cancel": "取消",
  "annotation.done": "完成",
  "annotation.color": "颜色",
  "annotation.size": "大小",
  "annotation.fill": "填充",
  "node.splitGrid": "拆分网格",
  "node.videoStitch": "视频拼接",
  "node.videoTrim": "视频裁剪",
  "node.easeCurve": "缓动曲线",
  "node.frameGrab": "抓取帧",
  "node.imageCompare": "图片对比",
  "imageCompare.connectTwo": "连接 2 张图片进行对比",
  "imageCompare.connectAnother": "再连接一张图片进行对比",
  "imageCompare.imageAConnected": "图片 A 已连接",
  "imageCompare.imageBConnected": "图片 B 已连接",
  "imageCompare.imageAAlt": "图片 A",
  "imageCompare.imageBAlt": "图片 B",
  "upload.imageUnsupported": "格式不支持。请使用 PNG、JPG 或 WebP。",
  "upload.imageTooLarge": "图片过大。最大支持 10MB。",
  "upload.videoUnsupported": "格式不支持。请使用 MP4、WebM 或 QuickTime 视频文件。",
  "upload.videoTooLarge": "视频文件过大。最大支持 200MB。",
  "upload.audioUnsupported": "格式不支持。请使用 MP3、WAV、OGG、AAC 或其他音频格式。",
  "upload.audioTooLarge": "音频文件过大。最大支持 50MB。",
  "imageInput.uploadedAlt": "已上传图片",
  "imageInput.download": "下载图片",
  "imageInput.remove": "移除图片",
  "videoInput.upload": "上传视频文件",
  "videoInput.dropVideo": "拖入视频或点击",
  "videoInput.download": "下载视频",
  "videoInput.remove": "移除视频",
  "audioInput.upload": "上传音频文件",
  "audioInput.dropAudio": "拖入音频或点击",
  "audioInput.download": "下载音频",
  "audioInput.remove": "移除音频",
  "audioInput.clear": "清除音频",
  "audioInput.loadingWaveform": "正在加载波形...",
  "generateAudio.fallbackUsed": "已使用备用模型",
  "generateAudio.fallbackUsedTitle": "主模型失败：{error}\n已使用备用模型：{model}",
  "generateAudio.generating": "正在生成音频...",
  "annotation.noImage": "没有可用图片。请连接图片或手动加载一张。",
  "annotation.annotatedAlt": "已标注图片",
  "annotation.downloadImage": "下载图片",
  "annotation.removeImage": "移除图片",
  "annotation.editCount": "编辑（{count}）",
  "annotation.addAnnotations": "添加标注",
  "annotation.dropClickConnect": "拖入、点击或连接",
  "array.split": "拆分",
  "array.delimiter": "分隔符",
  "array.newline": "换行",
  "array.regexAdvanced": "正则（高级）",
  "array.batch": "批量",
  "array.batchModeTitle": "批量模式：所有条目发送到同一个下游节点",
  "array.enableBatchMode": "启用批量模式",
  "array.autoRouteToPrompts": "自动路由到提示词",
  "array.by": "按",
  "array.advanced": "高级",
  "array.trim": "去除空白",
  "array.removeEmpty": "移除空项",
  "array.parsedItems": "解析条目（{count}）",
  "array.noItemsParsed": "尚未解析出条目",
  "array.selectedForNextConnection": "已选为下一条连接输出（点击取消）",
  "array.selectForNextConnection": "点击选择为下一条连接输出",
  "array.batchFooter": "批量：所有条目发送到下游节点",
  "array.nextWireUsesItem": "下一条连线使用第 {index} 条",
  "array.noSelection": "未选择：连线会从第 1 条开始依次使用",
  "videoCommon.encoderUnsupported": "当前浏览器不支持视频编码。",
  "videoCommon.encoderHelp": "看起来不对？请在 Discord 联系 Willie。",
  "videoCommon.checkingEncoder": "正在检查编码器...",
  "videoCommon.processingPercent": "处理中... {progress}%",
  "videoTrim.videoIn": "视频输入",
  "videoTrim.videoOut": "视频输出",
  "videoTrim.connectVideo": "连接一个视频进行裁剪",
  "videoTrim.source": "源视频",
  "videoTrim.trimmed": "已裁剪",
  "videoTrim.clearTrimmedVideo": "清除裁剪后视频",
  "videoTrim.start": "开始",
  "videoTrim.end": "结束",
  "videoTrim.duration": "时长",
  "videoTrim.trimStartTime": "裁剪开始时间",
  "videoTrim.trimEndTime": "裁剪结束时间",
  "videoTrim.setStartToCurrent": "将开始时间设为当前播放时间",
  "videoTrim.setEndToCurrent": "将结束时间设为当前播放时间",
  "videoTrim.trim": "裁剪",
  "videoStitch.videoInput": "视频 {index}",
  "videoStitch.audioInput": "音频",
  "videoStitch.output": "输出",
  "videoStitch.connectVideos": "连接视频进行拼接",
  "videoStitch.clipAlt": "片段 {id}",
  "videoStitch.clearVideo": "清除视频",
  "videoStitch.loop": "循环",
  "videoStitch.stitch": "拼接",
  "multiAngle.editorTitle": "多角度编辑器",
  "multiAngle.imageEditorTitle": "图片多角度编辑器",
  "multiAngle.imageTitle": "图片多角度",
  "multiAngle.closeEditor": "关闭多角度编辑器",
  "multiAngle.generate": "生成多角度",
  "multiAngle.noImageToast": "未找到可用图片，无法生成多角度图片",
  "multiAngle.sightControl": "多角度视线控制",
  "multiAngle.noImage": "无图片",
  "multiAngle.horizontal": "水平环绕",
  "multiAngle.vertical": "垂直俯仰",
  "multiAngle.zoom": "景别缩放",
  "multiAngle.prompt": "提示词",
  "multiAngle.enableCustomPrompt": "开启自定义提示词",
  "multiAngle.promptDialog": "多角度提示词",
  "multiAngle.promptInput": "多角度提示词输入",
  "multiAngle.promptPlaceholder": "输入提示词...",
  "multiAngle.reset": "重置参数",
  "multiAngle.resetAria": "重置多角度参数",
  "multiAngle.adjustVerticalUp": "向上调整垂直俯仰",
  "multiAngle.adjustVerticalDown": "向下调整垂直俯仰",
  "multiAngle.adjustHorizontalLeft": "向左调整水平环绕",
  "multiAngle.adjustHorizontalRight": "向右调整水平环绕",
  "multiAngle.preset.custom": "自定义",
  "multiAngle.preset.fisheye": "鱼眼视角",
  "multiAngle.preset.tilt": "倾斜视角",
  "multiAngle.preset.frontOverhead": "正面俯拍",
  "multiAngle.preset.frontLow": "正面仰拍",
  "multiAngle.preset.panoramicOverhead": "全景俯拍",
  "multiAngle.preset.back": "背面视角",
  "multiAngle.zoom.wide": "全景",
  "multiAngle.zoom.medium": "中景",
  "multiAngle.zoom.close": "特写",
  "workflow.splitGrid.noImage": "没有可拆分的图片。请先生成或加载一张图片。",
  "workflow.splitGrid.detectFailed": "无法识别图片网格。",
  "workflow.splitGrid.failed": "拆分图片网格失败：{error}",
  "workflow.generatedSuccess": "工作流生成成功",
  "workflow.generatedFailed": "生成工作流失败",
  "workflow.generatedFailedRetry": "生成工作流失败，请重试。",
  "workflow.appliedEdits": "已应用 {count} 个编辑",
  "chat.title": "画布助手",
  "chat.beta": "测试版",
  "chat.clear": "清空对话",
  "chat.close": "关闭对话",
  "chat.dismiss": "关闭",
  "chat.emptyTitle": "可以询问当前画布，也可以让我准备修改。",
  "chat.diagnoseSelected": "诊断选中节点",
  "chat.makeVariants": "生成变体",
  "chat.promptDiagnose": "诊断选中的节点，并给出最小可用修复建议。",
  "chat.promptVariants": "准备一组编辑：基于选中的图片或生成节点创建四个视觉变体。",
  "chat.proposalReady": "修改方案已准备",
  "chat.candidateReady": "候选工作流已准备好应用。",
  "chat.runReady": "运行操作已准备好应用。",
  "chat.candidateWorkflow": "候选工作流",
  "chat.applyWorkflow": "应用工作流",
  "chat.operationPreview": "操作预览",
  "chat.applyEdits": "应用编辑",
  "chat.runPreview": "运行预览",
  "chat.runNow": "立即运行",
  "chat.focusedNodes": "聚焦 {count} 个选中节点",
  "chat.runSelected": "运行选中",
  "chat.runAffected": "运行影响范围（{count}）",
  "chat.placeholder": "输入消息...",
  "chat.building": "正在构建...",
  "chat.buildWorkflow": "构建工作流",
  "chat.open": "助手",
  "chat.noSelection": "请先选择一个或多个节点。",
  "workflow.invalidWorkflowFile": "工作流文件格式无效",
  "workflow.parseWorkflowFileFailed": "解析工作流文件失败",
  "splitGridNode.sourceGridAlt": "源网格",
  "splitGridNode.connectImage": "连接图片",
  "splitGridNode.gridSummary": "{rows}x{cols} 网格（{count} 张图片）",
  "splitGridNode.generateSetsCreated": "已创建 {count} 组生成节点",
  "splitGridNode.notConfigured": "未配置 - 点击设置",
  "splitGridNode.configureFirst": "请先配置节点",
  "splitGridNode.connectImageFirst": "请先连接图片",
  "splitGridNode.splitGrid": "拆分网格",
  "splitGridNode.split": "拆分",
  "node.router": "路由器",
  "node.switch": "开关",
  "node.conditionalSwitch": "条件开关",
  "node.output": "输出",
  "node.outputGallery": "输出画廊",
  "output.downloadCurrent": "下载当前{type}",
  "output.downloadAll": "下载全部输出",
  "node.settings": "设置",
  "node.runToGenerate": "运行以生成",
  "node.selectModel": "选择模型...",
  "node.primary": "主模型",
  "node.text": "文本",
  "node.image": "图片",
  "node.notUsedByModel": "此模型不使用",
  "node.generationFailed": "生成失败",
  "node.videoGenerationFailed": "视频生成失败",
  "node.loadingParameters": "正在加载参数...",
  "node.noParameters": "无可用参数",
  "node.default": "默认",
  "node.model": "模型",
  "node.size": "尺寸",
  "node.quality": "质量",
  "node.voice": "声音",
  "node.sound": "声音",
  "node.duration": "时长",
  "node.seconds": "{count}秒",
  "node.aspectRatio": "宽高比",
  "node.resolution": "分辨率",
  "node.googleSearch": "Google 搜索",
  "node.imageSearch": "图片搜索",
  "node.provider": "服务商",
  "node.temperature": "温度：{value}",
  "node.maxTokens": "最大 Token：{value}",
  "node.generic": "节点",
  "splitGridSettings.title": "拆分网格设置",
  "splitGridSettings.gridLayout": "网格布局",
  "splitGridSettings.gridSummary": "网格将拆分为 {rows}x{cols} = {count} 张图片",
  "splitGridSettings.defaultPrompt": "默认提示词",
  "splitGridSettings.promptPlaceholder": "输入将应用到所有生成图片的提示词...",
  "splitGridSettings.promptHelp": "创建后，每个提示词节点都可以单独编辑",
  "splitGridSettings.generateNodeSettings": "生成节点设置",
  "splitGridSettings.createGenerateSets": "创建 {count} 组生成节点",
  "imageInput.upload": "上传图片",
  "imageInput.dropImage": "拖入图片",
  "dropOverlay.workflow": "拖入以加载工作流",
  "dropOverlay.node": "拖入以创建节点",
  "dropOverlay.audio": "拖入音频以创建节点",
  "dropOverlay.image": "拖入图片以创建节点",
  "dropOverlay.splittingImageGrid": "正在拆分图片网格...",
  "connectionMenu.addNode": "添加 {type} 节点",
  "connectionMenu.type.image": "图片",
  "connectionMenu.type.text": "文本",
  "connectionMenu.type.audio": "音频",
  "connectionMenu.type.video": "视频",
  "connectionMenu.type.3d": "3D",
  "connectionMenu.type.easeCurve": "设置",
  "connectionMenu.splitGridNode": "拆分网格节点",
  "connectionMenu.splitGridNow": "立即拆分网格",
  "connectionMenu.multiAngleImage": "图片多角度",
  "connectionMenu.navigate": "导航",
  "connectionMenu.select": "选择",
  "prompt.placeholder": "描述你想生成的内容...",
  "prompt.connectedPlaceholder": "来自连接节点的文本（可编辑）...",
  "prompt.optionalPlaceholder": "可选提示词（留空则跳过）...",
  "prompt.addVariable": "添加变量",
  "prompt.setVariableName": "设置变量名",
  "prompt.variableHelp": "将此提示词作为 PromptConstructor 节点中的变量使用",
  "prompt.variableName": "变量名",
  "prompt.variablePlaceholder": "例如 color、style、subject",
  "prompt.preview": "预览：{value}",
  "composer.promptProvidedByUpstream": "提示词由上游文本节点提供。",
  "composer.videoStitchClipCount": "{count} 个视频片段",
  "composer.videoStitchAudio": "已连接音频",
  "composer.videoStitchNoAudio": "无音频",
  "composer.videoStitchInputs": "视频输入",
  "composer.videoStitchInputHint": "在节点上调整片段顺序",
  "composer.videoStitchNeedTwo": "至少连接 2 个视频片段才能拼接。",
  "composer.videoStitchClip": "片段 {index}",
  "composer.videoStitchLoop": "循环",
  "composer.videoStitchOutputReady": "输出已就绪",
  "composer.videoStitchNoOutput": "尚未输出",
  "composer.videoStitchReady": "当前节点可拼接",
  "composer.videoStitchRun": "拼接当前视频节点",
  "composer.easeCurveVideoConnected": "已连接视频",
  "composer.easeCurveNoVideo": "连接一个视频输入后可应用缓动曲线。",
  "composer.easeCurveInherited": "继承设置",
  "composer.easeCurveLocal": "本地设置",
  "composer.easeCurveSettings": "曲线设置",
  "composer.easeCurvePreset": "预设",
  "composer.easeCurveCustom": "自定义贝塞尔",
  "composer.easeCurveHandles": "贝塞尔控制点",
  "composer.easeCurveInput": "输入",
  "composer.easeCurveDuration": "输出时长",
  "composer.easeCurveOutputReady": "输出已就绪",
  "composer.easeCurveNoOutput": "尚未输出",
  "composer.easeCurveReady": "当前曲线可应用",
  "composer.easeCurveRun": "应用当前缓动曲线节点",
  "composer.selectModel": "选择模型",
  "composer.chooseModelTitle": "选择生成模型",
  "composer.chooseImageModel": "选择图像模型",
  "composer.chooseVideoModel": "选择视频模型",
  "composer.chooseAudioModel": "选择声音模型",
  "composer.regenerateImageNode": "重新生成当前图片节点",
  "composer.regenerateVideoNode": "重新生成当前视频节点",
  "composer.regenerateAudioNode": "重新生成当前音频节点",
  "composer.imageNode": "图片节点",
  "composer.videoNode": "视频节点",
  "composer.audioNode": "声音节点",
  "composer.imageCount": "生成张数",
  "composer.imageCountOption": "{count}张",
  "composer.newGeneration": "新建生成",
  "composer.selectedNodes": "已选择 {count} 个节点",
  "composer.currentNode": "当前节点",
  "composer.createAndGenerate": "创建并生成",
  "composer.dragFromPort": "从节点端口拖拽以创建下一步",
  "composer.multiSelectNoSingleGenerate": "多选暂不支持单节点生成",
  "composer.retryCurrentNode": "重试当前节点",
  "composer.generate": "生成",
  "composer.describeContent": "描述你想生成的内容",
  "composer.mainPlaceholder": "描述你想生成的内容，按 / 呼出指令，@ 引用素材",
  "composer.uploadReferenceImage": "上传参考图片",
  "composer.expand": "展开底部栏",
  "composer.collapse": "收起底部栏",
  "composer.console": "控制台",
  "composer.style": "风格",
  "composer.mark": "标记",
  "composer.focus": "聚焦",
  "composer.addReferenceImage": "添加参考图片",
  "composer.referenceFromUpstream": "参考图片来自上游连接",
  "composer.reference": "参考",
  "composer.referenceImage": "参考图片 {index}",
  "composer.removeReferenceImage": "移除参考图片 {index}",
  "composer.commands": "指令",
  "composer.referenceMaterials": "引用素材",
  "composer.commandCharacter": "生成角色图",
  "composer.commandCharacterValue": "生成角色图，保持造型统一，画面清晰。",
  "composer.commandTurnaround": "生成三视图",
  "composer.commandTurnaroundValue": "生成角色三视图，包含正面、侧面、背面。",
  "composer.commandVideoShot": "转视频镜头",
  "composer.commandVideoShotValue": "将当前画面转成短视频镜头，动作自然。",
  "composer.currentReference": "当前引用素材",
  "composer.canvasAssets": "画布素材",
  "composer.multiSelectUnsupported": "已选择 {count} 个节点。当前版本暂不支持批量编辑。",
  "composer.unsupportedNodeHint": "下一步需要从节点端口拖拽创建，底部框只编辑当前选中的生成节点。",
  "composer.videoSchemaHint": "视频参数随模型 schema 生效",
  "composer.translate": "翻译",
  "composer.settings": "设置",
  "composer.selectModelTitle": "选择模型：{model}",
  "shortcuts.title": "快捷键",
  "shortcuts.general": "通用",
  "shortcuts.addNodes": "添加节点",
  "shortcuts.layout": "布局（先选择 2 个以上节点）",
  "shortcuts.canvas": "画布",
  "shortcuts.runWorkflow": "运行工作流",
  "shortcuts.copyNodes": "复制选中节点",
  "shortcuts.paste": "粘贴节点 / 图片 / 文本",
  "shortcuts.undo": "撤销",
  "shortcuts.redo": "重做",
  "shortcuts.showShortcuts": "显示快捷键",
  "shortcuts.addPrompt": "添加提示词节点",
  "shortcuts.addImage": "添加图片输入节点",
  "shortcuts.addGenerateImage": "添加图片生成节点",
  "shortcuts.addGenerateVideo": "添加视频生成节点",
  "shortcuts.addLLM": "添加 LLM 文本节点",
  "shortcuts.addAnnotation": "添加标注节点",
  "shortcuts.addAudio": "添加音频节点",
  "shortcuts.addVideoInput": "添加视频输入节点",
  "shortcuts.addArray": "添加数组节点",
  "shortcuts.stackVertical": "垂直排列选中节点",
  "shortcuts.stackHorizontal": "水平排列选中节点",
  "shortcuts.arrangeGrid": "将选中节点排成网格",
  "shortcuts.zoom": "放大 / 缩小",
  "shortcuts.pan": "平移（macOS）",
  "shortcuts.delete": "删除选中节点",
  "project.newProject": "新建项目",
  "project.settings": "项目设置",
  "project.tabProject": "项目",
  "project.tabProviders": "服务商",
  "project.tabNodeDefaults": "节点默认值",
  "project.tabCanvas": "画布",
  "project.name": "项目名称",
  "project.directory": "项目目录",
  "project.localFolderPlaceholder": "选择这台设备上的本地文件夹",
  "project.directoryHelp": "工作流文件和图片会保存在这里，并自动创建 inputs 与 generations 子文件夹。",
  "project.embedImages": "将图片嵌入为 base64",
  "project.embedImagesDesc": "把所有图片嵌入工作流文件，文件会变大；超大工作流可能触达内存限制。",
  "project.inlineSettings": "在节点上显示模型设置",
  "project.inlineSettingsDesc": "在生成节点内部显示模型参数，而不是侧边面板",
  "project.envHelp": "建议通过 .env.local 添加 API 密钥以提升安全性；这里添加的密钥会覆盖 .env，并存储在浏览器中。",
  "project.newapiwgDesc": "Popi 统一模型网关",
  "project.baseUrl": "Base URL",
  "project.systemImageDefault": "系统默认（Popi Models）",
  "project.noneSetFirstUse": "未设置（首次使用时选择）",
  "project.defaultLLM": "默认 LLM 设置",
  "project.systemLLMDefault": "使用系统默认值（Popi Models Doubao Seed 2.0 Lite）",
  "project.provider": "服务商",
  "project.model": "模型",
  "project.temp": "温度：{value}",
  "project.tokens": "Token：{value}",
  "project.executionSettings": "执行设置",
  "project.maxParallel": "最大并行调用：{count}",
  "project.executionHelp": "工作流执行时最多并行运行的节点数量。数值更高可能更快，但也更容易触发 API 限流。",
  "project.defaultsHelp": "这些默认值会在通过快捷键创建节点时应用（Shift+G、Shift+L 等）。",
  "project.canvasHelp": "配置画布导航和交互方式。",
  "project.canvasAssistant": "画布助手",
  "project.canvasAssistantShort": "助手",
  "project.panMode": "平移模式",
  "project.panSpace": "按住 Space 并拖拽平移",
  "project.panMiddle": "按住鼠标中键并拖拽",
  "project.panAlways": "无需按键即可平移",
  "project.panSpaceLabel": "Space + 拖拽",
  "project.panMiddleLabel": "鼠标中键",
  "project.panAlwaysLabel": "始终开启",
  "project.zoomMode": "缩放模式",
  "project.zoomAlt": "按住 Alt 并滚动缩放",
  "project.zoomCtrl": "按住 Ctrl/Cmd 并滚动缩放",
  "project.zoomScroll": "直接滚动缩放",
  "project.zoomAltLabel": "Alt + 滚动",
  "project.zoomCtrlLabel": "Ctrl + 滚动",
  "project.zoomScrollLabel": "滚动",
  "project.selectionMode": "选择模式",
  "project.selectClick": "点击选择节点",
  "project.selectAlt": "按住 Alt 并拖拽选择",
  "project.selectShift": "按住 Shift 并拖拽选择",
  "project.selectClickLabel": "点击",
  "project.selectAltLabel": "Alt + 拖拽",
  "project.selectShiftLabel": "Shift + 拖拽",
  "project.validating": "验证中...",
  "project.errorOpenDir": "打开目录选择器失败",
  "project.errorOpenDirWithReason": "打开目录选择器失败：{error}",
  "project.errorNameRequired": "项目名称必填",
  "project.errorDirectoryRequired": "项目目录必填",
  "project.errorChooseLocalFolder": "请从浏览器选择本地文件夹，这样项目会保存在当前操作者的设备上。",
  "project.errorAbsolutePath": "项目目录必须是绝对路径（以 /、盘符或 UNC 路径开头）",
  "project.errorNotDirectory": "项目路径不是目录",
  "project.errorValidateDir": "验证目录失败：{error}",
  "modelSearch.search": "搜索模型...",
  "modelSearch.allProviders": "所有服务商",
  "modelSearch.all": "全部",
  "modelSearch.allTypes": "所有类型",
  "modelSearch.image": "图片",
  "modelSearch.video": "视频",
  "modelSearch.audio": "音频",
  "modelSearch.refresh": "刷新模型和参数",
  "modelSearch.loading": "正在加载模型...",
  "modelSearch.tryAgain": "重试",
  "modelSearch.removeFallback": "移除备用模型",
  "modelSearch.clearSelection": "清除当前选择",
  "modelSearch.noModels": "未找到模型",
  "modelSearch.adjustFilters": "尝试调整搜索或筛选条件",
  "modelSearch.recent": "最近使用",
  "promptWorkflow.title": "用提示生成工作流",
  "promptWorkflow.describe": "描述你的工作流",
  "promptWorkflow.placeholder": "例如：基于参考图创建光照和风格一致的产品摄影...",
  "promptWorkflow.help": "描述你希望工作流完成什么。请具体说明输入、输出和任何转换步骤。",
  "promptWorkflow.note": "注意：此功能目前仅适用于 Gemini 模型。",
  "promptWorkflow.generating": "生成中...",
  "promptWorkflow.generate": "生成工作流",
  "promptWorkflow.dismiss": "关闭提示",
  "promptWorkflow.validation": "请描述你的工作流（至少 3 个字符）",
  "promptWorkflow.failed": "生成工作流失败",
  "workflowBrowser.noWorkflows": "未找到工作流",
  "workflowBrowser.noWorkflowsDesc": "此文件夹不包含任何工作流项目",
  "workflowBrowser.title": "你的工作流",
  "workflowBrowser.chooseFolderDesc": "选择包含工作流项目的文件夹。你可以稍后再更改。",
  "workflowBrowser.chooseFolder": "选择文件夹",
  "workflowBrowser.projectCount": "{count} 个项目",
  "workflowBrowser.openFromDirectory": "从目录打开",
  "workflowBrowser.changeFolder": "更改文件夹",
  "workflowBrowser.justNow": "刚刚",
  "workflowBrowser.minutesAgo": "{count} 分钟前",
  "workflowBrowser.hoursAgo": "{count} 小时前",
  "workflowBrowser.daysAgo": "{count} 天前",
  "workflowBrowser.monthsAgo": "{count} 个月前",
  "workflowBrowser.errorNoWorkflows": "此文件夹中未找到工作流",
  "workflowBrowser.errorList": "列出工作流失败",
  "workflowBrowser.errorFetch": "获取工作流失败",
  "multiSelect.stackHorizontal": "水平排列 (H)",
  "multiSelect.stackVertical": "垂直排列 (V)",
  "multiSelect.arrangeGrid": "排列为网格 (G)",
  "multiSelect.removeGroup": "从分组中移除",
  "multiSelect.createGroup": "创建分组",
  "multiSelect.downloadZip": "下载图片 ZIP",
  "cost.viewDetails": "查看费用详情",
};

const zhTW: Dictionary = {
  ...zhCN,
  "language.label": "語言",
  "common.back": "返回",
  "common.next": "下一步",
  "common.cancel": "取消",
  "common.close": "關閉",
  "common.save": "儲存",
  "common.create": "建立",
  "workflowBrowser.title": "你的工作流程",
  "workflowBrowser.chooseFolderDesc": "選擇包含工作流程專案的資料夾。你可以稍後再變更。",
  "workflowBrowser.chooseFolder": "選擇資料夾",
  "workflowBrowser.projectCount": "{count} 個專案",
  "workflowBrowser.openFromDirectory": "從目錄開啟",
  "workflowBrowser.changeFolder": "變更資料夾",
  "workflowBrowser.justNow": "剛剛",
  "workflowBrowser.minutesAgo": "{count} 分鐘前",
  "workflowBrowser.hoursAgo": "{count} 小時前",
  "workflowBrowser.daysAgo": "{count} 天前",
  "workflowBrowser.monthsAgo": "{count} 個月前",
  "workflowBrowser.errorNoWorkflows": "此資料夾中找不到工作流程",
  "workflowBrowser.errorList": "列出工作流程失敗",
  "workflowBrowser.errorFetch": "取得工作流程失敗",
  "common.browse": "瀏覽",
  "common.change": "變更",
  "common.clear": "清除",
  "common.select": "選擇",
  "common.selectModel": "選擇模型",
  "common.skip": "略過",
  "common.show": "顯示",
  "common.hide": "隱藏",
  "common.override": "覆寫",
  "common.configuredViaEnv": "已透過 .env 設定",
  "common.recommended": "推薦",
  "common.optional": "可選",
  "common.required": "必填",
  "common.download": "下載",
  "common.remove": "移除",
  "common.disconnect": "斷開連接",
  "common.play": "播放",
  "common.pause": "暫停",
  "common.set": "設定",
  "common.previous": "上一個",
  "common.nextItem": "下一個",
  "common.processing": "處理中...",
  "error.providerOverloaded": "{model}：服務商暫時過載。請稍後重試，或切換到其他模型。",
  "node.optionalTitle": "此輸入為可選，空輸入會被略過",
  "node.markOptionalTitle": "標記為可選，空輸入會略過這個分支",
  "header.openWelcome": "開啟歡迎頁",
  "header.untitled": "未命名",
  "header.saving": "儲存中...",
  "header.savedAt": "已儲存 {time}",
  "header.notSaved": "未儲存",
  "header.saveProject": "儲存專案",
  "header.configureSaveLocation": "設定儲存位置",
  "header.openProject": "開啟專案",
  "header.openProjectFolder": "開啟專案資料夾",
  "header.projectSettings": "專案設定",
  "header.revertAIChanges": "還原 AI 變更",
  "header.revertAIChangesTitle": "恢復到 AI 變更前的工作流程",
  "header.revertConfirm": "確定嗎？這會恢復先前的工作流程。",
  "header.shortcuts": "鍵盤快捷鍵 (?)",
  "header.commentsTitle": "{unviewed} 則未讀評論（共 {total} 則）",
  "quickstart.description": "一個面向生成式 AI 流程的節點式工作流程編輯器。連接節點即可建立生成與轉換圖片、影片、音訊和 3D 資產的流程。",
  "quickstart.docs": "文件",
  "quickstart.waitlist": "NB Pro 候補名單",
  "quickstart.newProject": "新增專案",
  "quickstart.newProjectDesc": "開始新的工作流程",
  "quickstart.loadWorkflow": "載入工作流程",
  "quickstart.loadWorkflowDesc": "開啟既有檔案",
  "quickstart.templates": "範本",
  "quickstart.templatesDesc": "預建工作流程",
  "quickstart.promptWorkflow": "用提示生成工作流程",
  "quickstart.promptWorkflowDesc": "讓 Gemini 幫你建立",
  "quickstart.beta": "測試版",
  "quickstart.templateExplorerTitle": "範本瀏覽器",
  "quickstart.workflowTemplatesTitle": "工作流程範本",
  "quickstart.templatesIntro": "使用預建工作流程快速開始。選擇一個範本即可載入畫布。",
  "quickstart.quickStart": "快速開始",
  "quickstart.searchTemplates": "搜尋範本",
  "quickstart.searchTemplatesPlaceholder": "搜尋範本...",
  "quickstart.category": "分類",
  "quickstart.provider": "服務商",
  "quickstart.filterAll": "全部",
  "quickstart.categorySimple": "簡單",
  "quickstart.categoryAdvanced": "進階",
  "quickstart.clearFilters": "清除篩選",
  "quickstart.clearAllFilters": "清除全部篩選",
  "quickstart.noTemplatesMatch": "沒有符合條件的範本",
  "quickstart.adjustSearchFilters": "嘗試調整搜尋或篩選條件",
  "quickstart.useWorkflow": "使用工作流程",
  "quickstart.loading": "載入中...",
  "quickstart.nodeCount": "{count} 個節點",
  "quickstart.dismiss": "關閉",
  "quickstart.loadTemplateFailed": "載入範本失敗",
  "quickstart.template.productShot.name": "產品圖",
  "quickstart.template.productShot.description": "將產品放入新的場景或環境",
  "quickstart.template.modelProduct.name": "模特 + 產品",
  "quickstart.template.modelProduct.description": "組合模特、產品和場景",
  "quickstart.template.colorVariations.name": "顏色變體",
  "quickstart.template.colorVariations.description": "基於參考圖生成產品配色變體",
  "quickstart.template.backgroundSwap.name": "替換背景",
  "quickstart.template.backgroundSwap.description": "將主體放入新的背景",
  "quickstart.template.styleTransfer.name": "風格轉換",
  "quickstart.template.styleTransfer.description": "將一張圖的風格套用到另一張圖",
  "quickstart.template.sceneComposite.name": "場景合成",
  "quickstart.template.sceneComposite.description": "組合多個場景中的元素",
  "ftux.welcomeTo": "歡迎使用 popiart-node",
  "ftux.stepApiKeys": "API 金鑰",
  "ftux.stepModelDefaults": "模型預設值",
  "ftux.stepReady": "就緒",
  "ftux.getStarted": "開始使用",
  "ftux.skipSetup": "略過設定？",
  "ftux.skipSetupDesc": "稍後可在設定中配置 API 金鑰和模型預設值。",
  "ftux.welcomeTitle": "開始吧。",
  "ftux.welcomeDesc": "像積木一樣連接 AI 模型，生成圖片、影片等內容。",
  "ftux.apiKeysDesc": "在這裡新增金鑰以使用 AI 服務商（儲存在瀏覽器），也可以寫入 .env 檔案以獲得更好的安全性與持久性。",
  "ftux.enterKey": "輸入金鑰...",
  "ftux.modelsTitle": "選擇你的模型",
  "ftux.modelsDesc": "選擇圖片和影片的預設 AI 模型，之後可以隨時變更。",
  "ftux.defaultImageModel": "預設圖片模型",
  "ftux.defaultVideoModel": "預設影片模型",
  "ftux.noneSet": "未設定",
  "ftux.readyTitle": "準備好了！",
  "ftux.readyDesc": "要快速看一遍教學嗎？",
  "ftux.startTutorial": "開始教學",
  "ftux.tutorial.welcome": "我們先快速了解基本操作。",
  "ftux.tutorial.addImage": "點擊「圖片」按鈕，新增一個圖片節點。",
  "ftux.tutorial.explainNode": "這是一個節點。每個節點都有特定功能。\n\n這個節點用於新增圖片。",
  "ftux.tutorial.explainNodeInputs": "輸入端口一律在節點左側。",
  "ftux.tutorial.explainNodeOutputs": "輸出端口一律在節點右側。",
  "ftux.tutorial.dragAndDrop": "現在從輸出端口拖曳，並釋放到畫布空白處。",
  "ftux.tutorial.selectGenerateImage": "這個選單會顯示該節點可連接的所有節點。\n\n選擇「生成圖片」，新增一個 AI 圖片生成節點。",
  "ftux.tutorial.addPromptNode": "點擊「提示詞」按鈕，新增一個提示詞節點。",
  "ftux.tutorial.connectPromptToGenerate": "從提示詞節點的輸出端口拖到生成圖片節點的輸入端口。",
  "ftux.tutorial.populateContent": "我來幫你填入一些範例內容 🎨",
  "ftux.tutorial.explainGenerateNode": "這是生成圖片節點。它會根據提示詞和參考圖，用 AI 建立或修改圖片。",
  "ftux.tutorial.explainRunButton": "點擊這個「執行」按鈕會執行工作流程。你也可以按 Cmd+Enter（Windows 上為 Ctrl+Enter）。",
  "ftux.tutorial.explainRunOptions": "你也可以點擊下拉選單，從指定節點開始執行，或只執行選中的節點。",
  "ftux.tutorial.runWorkflow": "現在執行你的工作流程！點擊「執行」按鈕生成圖片。",
  "ftux.tutorial.demonstrateDownstream": "現在看看還能繼續做什麼... 🎬✨",
  "ftux.tutorial.demonstrateComplete": "繼續向下游連接更多節點來建立生成式流程，也可以把它當作無限創意畫布使用。",
  "ftux.tutorial.saveProject": "儲存專案後，你的工作和生成結果都會保存在本機。",
  "ftux.tutorial.resources": "下面這些資源可以提供幫助和靈感：",
  "ftux.tutorial.discord": "加入我們的 Discord 社群",
  "ftux.tutorial.complete": "全部設定好了！開始創作吧。",
  "ftux.tutorial.clickToContinue": "點擊任意位置繼續",
  "ftux.tutorial.skip": "略過教學",
  "toolbar.categoryInput": "輸入",
  "toolbar.categoryText": "文字",
  "toolbar.categoryGenerate": "生成",
  "toolbar.categoryProcess": "處理",
  "toolbar.categoryRoute": "路由",
  "toolbar.categoryOutput": "輸出",
  "toolbar.image": "圖片",
  "toolbar.video": "影片",
  "toolbar.prompt": "提示詞",
  "toolbar.allNodes": "所有節點",
  "toolbar.allModels": "所有模型",
  "toolbar.browseModels": "瀏覽模型",
  "toolbar.switchConnectors": "切換為{style}連線",
  "toolbar.curved": "曲線",
  "toolbar.angular": "折線",
  "toolbar.runningNodes": "正在執行 {count} 個節點...",
  "toolbar.nodesCount": "{count} 個節點",
  "toolbar.runEntire": "執行整個工作流程",
  "toolbar.runFromSelected": "從選中節點開始執行",
  "toolbar.runSelectedOnly": "僅執行選中節點",
  "toolbar.runSelectedNodes": "執行選中節點",
  "toolbar.runSelectedNodesCount": "執行 {count} 個選中節點",
  "node.imageInput": "圖片輸入",
  "node.audioInput": "音訊輸入",
  "node.videoInput": "影片輸入",
  "node.generateImage": "生成圖片",
  "node.generateVideo": "生成影片",
  "node.generateAudio": "生成音訊",
  "node.annotation": "標註",
  "annotation.tool.select": "選擇",
  "annotation.tool.rectangle": "矩形",
  "annotation.tool.circle": "圓形",
  "annotation.tool.arrow": "箭頭",
  "annotation.tool.draw": "繪製",
  "annotation.tool.text": "文字",
  "annotation.undo": "復原",
  "annotation.redo": "重做",
  "annotation.clear": "清除",
  "annotation.cancel": "取消",
  "annotation.done": "完成",
  "annotation.color": "顏色",
  "annotation.size": "大小",
  "annotation.fill": "填充",
  "node.outputGallery": "輸出圖庫",
  "imageCompare.connectTwo": "連接 2 張圖片進行對比",
  "imageCompare.connectAnother": "再連接一張圖片進行對比",
  "imageCompare.imageAConnected": "圖片 A 已連接",
  "imageCompare.imageBConnected": "圖片 B 已連接",
  "imageCompare.imageAAlt": "圖片 A",
  "imageCompare.imageBAlt": "圖片 B",
  "upload.imageUnsupported": "格式不支援。請使用 PNG、JPG 或 WebP。",
  "upload.imageTooLarge": "圖片過大。最大支援 10MB。",
  "upload.videoUnsupported": "格式不支援。請使用 MP4、WebM 或 QuickTime 影片檔。",
  "upload.videoTooLarge": "影片檔過大。最大支援 200MB。",
  "upload.audioUnsupported": "格式不支援。請使用 MP3、WAV、OGG、AAC 或其他音訊格式。",
  "upload.audioTooLarge": "音訊檔過大。最大支援 50MB。",
  "imageInput.uploadedAlt": "已上傳圖片",
  "imageInput.download": "下載圖片",
  "imageInput.remove": "移除圖片",
  "videoInput.upload": "上傳影片檔",
  "videoInput.dropVideo": "拖入影片或點擊",
  "videoInput.download": "下載影片",
  "videoInput.remove": "移除影片",
  "audioInput.upload": "上傳音訊檔",
  "audioInput.dropAudio": "拖入音訊或點擊",
  "audioInput.download": "下載音訊",
  "audioInput.remove": "移除音訊",
  "audioInput.clear": "清除音訊",
  "audioInput.loadingWaveform": "正在載入波形...",
  "generateAudio.fallbackUsed": "已使用備用模型",
  "generateAudio.fallbackUsedTitle": "主模型失敗：{error}\n已使用備用模型：{model}",
  "generateAudio.generating": "正在生成音訊...",
  "annotation.noImage": "沒有可用圖片。請連接圖片或手動載入一張。",
  "annotation.annotatedAlt": "已標註圖片",
  "annotation.downloadImage": "下載圖片",
  "annotation.removeImage": "移除圖片",
  "annotation.editCount": "編輯（{count}）",
  "annotation.addAnnotations": "新增標註",
  "annotation.dropClickConnect": "拖入、點擊或連接",
  "array.split": "拆分",
  "array.delimiter": "分隔符號",
  "array.newline": "換行",
  "array.regexAdvanced": "正則（進階）",
  "array.batch": "批次",
  "array.batchModeTitle": "批次模式：所有項目傳送到同一個下游節點",
  "array.enableBatchMode": "啟用批次模式",
  "array.autoRouteToPrompts": "自動路由到提示詞",
  "array.by": "依",
  "array.advanced": "進階",
  "array.trim": "去除空白",
  "array.removeEmpty": "移除空項",
  "array.parsedItems": "解析項目（{count}）",
  "array.noItemsParsed": "尚未解析出項目",
  "array.selectedForNextConnection": "已選為下一條連接輸出（點擊取消）",
  "array.selectForNextConnection": "點擊選擇為下一條連接輸出",
  "array.batchFooter": "批次：所有項目傳送到下游節點",
  "array.nextWireUsesItem": "下一條連線使用第 {index} 項",
  "array.noSelection": "未選擇：連線會從第 1 項開始依序使用",
  "videoCommon.encoderUnsupported": "目前瀏覽器不支援影片編碼。",
  "videoCommon.encoderHelp": "看起來不對？請在 Discord 聯絡 Willie。",
  "videoCommon.checkingEncoder": "正在檢查編碼器...",
  "videoCommon.processingPercent": "處理中... {progress}%",
  "videoTrim.videoIn": "影片輸入",
  "videoTrim.videoOut": "影片輸出",
  "videoTrim.connectVideo": "連接一個影片進行裁剪",
  "videoTrim.source": "來源影片",
  "videoTrim.trimmed": "已裁剪",
  "videoTrim.clearTrimmedVideo": "清除裁剪後影片",
  "videoTrim.start": "開始",
  "videoTrim.end": "結束",
  "videoTrim.duration": "時長",
  "videoTrim.trimStartTime": "裁剪開始時間",
  "videoTrim.trimEndTime": "裁剪結束時間",
  "videoTrim.setStartToCurrent": "將開始時間設為目前播放時間",
  "videoTrim.setEndToCurrent": "將結束時間設為目前播放時間",
  "videoTrim.trim": "裁剪",
  "videoStitch.videoInput": "影片 {index}",
  "videoStitch.audioInput": "音訊",
  "videoStitch.output": "輸出",
  "videoStitch.connectVideos": "連接影片進行拼接",
  "videoStitch.clipAlt": "片段 {id}",
  "videoStitch.clearVideo": "清除影片",
  "videoStitch.loop": "循環",
  "videoStitch.stitch": "拼接",
  "multiAngle.editorTitle": "多角度編輯器",
  "multiAngle.imageEditorTitle": "圖片多角度編輯器",
  "multiAngle.imageTitle": "圖片多角度",
  "multiAngle.closeEditor": "關閉多角度編輯器",
  "multiAngle.generate": "生成多角度",
  "multiAngle.noImageToast": "未找到可用圖片，無法生成多角度圖片",
  "multiAngle.sightControl": "多角度視線控制",
  "multiAngle.noImage": "無圖片",
  "multiAngle.horizontal": "水平環繞",
  "multiAngle.vertical": "垂直俯仰",
  "multiAngle.zoom": "景別縮放",
  "multiAngle.prompt": "提示詞",
  "multiAngle.enableCustomPrompt": "開啟自訂提示詞",
  "multiAngle.promptDialog": "多角度提示詞",
  "multiAngle.promptInput": "多角度提示詞輸入",
  "multiAngle.promptPlaceholder": "輸入提示詞...",
  "multiAngle.reset": "重置參數",
  "multiAngle.resetAria": "重置多角度參數",
  "multiAngle.adjustVerticalUp": "向上調整垂直俯仰",
  "multiAngle.adjustVerticalDown": "向下調整垂直俯仰",
  "multiAngle.adjustHorizontalLeft": "向左調整水平環繞",
  "multiAngle.adjustHorizontalRight": "向右調整水平環繞",
  "multiAngle.preset.custom": "自訂",
  "multiAngle.preset.fisheye": "魚眼視角",
  "multiAngle.preset.tilt": "傾斜視角",
  "multiAngle.preset.frontOverhead": "正面俯拍",
  "multiAngle.preset.frontLow": "正面仰拍",
  "multiAngle.preset.panoramicOverhead": "全景俯拍",
  "multiAngle.preset.back": "背面視角",
  "multiAngle.zoom.wide": "全景",
  "multiAngle.zoom.medium": "中景",
  "multiAngle.zoom.close": "特寫",
  "workflow.splitGrid.noImage": "沒有可拆分的圖片。請先生成或載入一張圖片。",
  "workflow.splitGrid.detectFailed": "無法識別圖片網格。",
  "workflow.splitGrid.failed": "拆分圖片網格失敗：{error}",
  "workflow.generatedSuccess": "工作流程生成成功",
  "workflow.generatedFailed": "生成工作流程失敗",
  "workflow.generatedFailedRetry": "生成工作流程失敗，請重試。",
  "workflow.appliedEdits": "已套用 {count} 個編輯",
  "chat.title": "畫布助手",
  "chat.beta": "測試版",
  "chat.clear": "清空對話",
  "chat.close": "關閉對話",
  "chat.dismiss": "關閉",
  "chat.emptyTitle": "可以詢問目前畫布，也可以讓我準備修改。",
  "chat.diagnoseSelected": "診斷選中節點",
  "chat.makeVariants": "生成變體",
  "chat.promptDiagnose": "診斷選中的節點，並給出最小可用修復建議。",
  "chat.promptVariants": "準備一組編輯：基於選中的圖片或生成節點建立四個視覺變體。",
  "chat.proposalReady": "修改方案已準備",
  "chat.candidateReady": "候選工作流程已準備好套用。",
  "chat.runReady": "執行操作已準備好套用。",
  "chat.candidateWorkflow": "候選工作流程",
  "chat.applyWorkflow": "套用工作流程",
  "chat.operationPreview": "操作預覽",
  "chat.applyEdits": "套用編輯",
  "chat.runPreview": "執行預覽",
  "chat.runNow": "立即執行",
  "chat.focusedNodes": "聚焦 {count} 個選中節點",
  "chat.runSelected": "執行選中",
  "chat.runAffected": "執行影響範圍（{count}）",
  "chat.placeholder": "輸入訊息...",
  "chat.building": "正在建立...",
  "chat.buildWorkflow": "建立工作流程",
  "chat.open": "助手",
  "chat.noSelection": "請先選擇一個或多個節點。",
  "workflow.invalidWorkflowFile": "工作流程檔案格式無效",
  "workflow.parseWorkflowFileFailed": "解析工作流程檔案失敗",
  "splitGridNode.sourceGridAlt": "來源網格",
  "splitGridNode.connectImage": "連接圖片",
  "splitGridNode.gridSummary": "{rows}x{cols} 網格（{count} 張圖片）",
  "splitGridNode.generateSetsCreated": "已建立 {count} 組生成節點",
  "splitGridNode.notConfigured": "未設定 - 點擊設定",
  "splitGridNode.configureFirst": "請先設定節點",
  "splitGridNode.connectImageFirst": "請先連接圖片",
  "splitGridNode.splitGrid": "拆分網格",
  "splitGridNode.split": "拆分",
  "output.downloadCurrent": "下載目前{type}",
  "output.downloadAll": "下載全部輸出",
  "node.settings": "設定",
  "node.runToGenerate": "執行以生成",
  "node.selectModel": "選擇模型...",
  "node.primary": "主模型",
  "node.text": "文字",
  "node.image": "圖片",
  "node.notUsedByModel": "此模型不使用",
  "node.generationFailed": "生成失敗",
  "node.videoGenerationFailed": "影片生成失敗",
  "node.loadingParameters": "正在載入參數...",
  "node.noParameters": "無可用參數",
  "node.default": "預設",
  "node.model": "模型",
  "node.size": "尺寸",
  "node.quality": "品質",
  "node.voice": "聲音",
  "node.sound": "聲音",
  "node.duration": "時長",
  "node.seconds": "{count}秒",
  "node.aspectRatio": "寬高比",
  "node.resolution": "解析度",
  "node.googleSearch": "Google 搜尋",
  "node.imageSearch": "圖片搜尋",
  "node.provider": "服務商",
  "node.temperature": "溫度：{value}",
  "node.maxTokens": "最大 Token：{value}",
  "node.generic": "節點",
  "splitGridSettings.title": "拆分網格設定",
  "splitGridSettings.gridLayout": "網格版面",
  "splitGridSettings.gridSummary": "網格將拆分為 {rows}x{cols} = {count} 張圖片",
  "splitGridSettings.defaultPrompt": "預設提示詞",
  "splitGridSettings.promptPlaceholder": "輸入將套用到所有生成圖片的提示詞...",
  "splitGridSettings.promptHelp": "建立後，每個提示詞節點都可以個別編輯",
  "splitGridSettings.generateNodeSettings": "生成節點設定",
  "splitGridSettings.createGenerateSets": "建立 {count} 組生成節點",
  "imageInput.upload": "上傳圖片",
  "imageInput.dropImage": "拖入圖片",
  "dropOverlay.workflow": "拖入以載入工作流程",
  "dropOverlay.node": "拖入以建立節點",
  "dropOverlay.audio": "拖入音訊以建立節點",
  "dropOverlay.image": "拖入圖片以建立節點",
  "dropOverlay.splittingImageGrid": "正在拆分圖片網格...",
  "connectionMenu.addNode": "新增 {type} 節點",
  "connectionMenu.type.image": "圖片",
  "connectionMenu.type.text": "文字",
  "connectionMenu.type.audio": "音訊",
  "connectionMenu.type.video": "影片",
  "connectionMenu.type.3d": "3D",
  "connectionMenu.type.easeCurve": "設定",
  "connectionMenu.splitGridNode": "拆分網格節點",
  "connectionMenu.splitGridNow": "立即拆分網格",
  "connectionMenu.multiAngleImage": "圖片多角度",
  "connectionMenu.navigate": "導覽",
  "connectionMenu.select": "選擇",
  "prompt.placeholder": "描述你想生成的內容...",
  "prompt.connectedPlaceholder": "來自連接節點的文字（可編輯）...",
  "prompt.optionalPlaceholder": "可選提示詞（留空則略過）...",
  "prompt.addVariable": "新增變數",
  "prompt.setVariableName": "設定變數名稱",
  "prompt.variableHelp": "將此提示詞作為 PromptConstructor 節點中的變數使用",
  "prompt.variableName": "變數名稱",
  "prompt.variablePlaceholder": "例如 color、style、subject",
  "prompt.preview": "預覽：{value}",
  "composer.promptProvidedByUpstream": "提示詞由上游文字節點提供。",
  "composer.videoStitchClipCount": "{count} 個影片片段",
  "composer.videoStitchAudio": "已連接音訊",
  "composer.videoStitchNoAudio": "無音訊",
  "composer.videoStitchInputs": "影片輸入",
  "composer.videoStitchInputHint": "在節點上調整片段順序",
  "composer.videoStitchNeedTwo": "至少連接 2 個影片片段才能拼接。",
  "composer.videoStitchClip": "片段 {index}",
  "composer.videoStitchLoop": "循環",
  "composer.videoStitchOutputReady": "輸出已就緒",
  "composer.videoStitchNoOutput": "尚未輸出",
  "composer.videoStitchReady": "目前節點可拼接",
  "composer.videoStitchRun": "拼接目前影片節點",
  "composer.easeCurveVideoConnected": "已連接影片",
  "composer.easeCurveNoVideo": "連接一個影片輸入後可套用緩動曲線。",
  "composer.easeCurveInherited": "繼承設定",
  "composer.easeCurveLocal": "本機設定",
  "composer.easeCurveSettings": "曲線設定",
  "composer.easeCurvePreset": "預設",
  "composer.easeCurveCustom": "自訂貝塞爾",
  "composer.easeCurveHandles": "貝塞爾控制點",
  "composer.easeCurveInput": "輸入",
  "composer.easeCurveDuration": "輸出時長",
  "composer.easeCurveOutputReady": "輸出已就緒",
  "composer.easeCurveNoOutput": "尚未輸出",
  "composer.easeCurveReady": "目前曲線可套用",
  "composer.easeCurveRun": "套用目前緩動曲線節點",
  "shortcuts.title": "鍵盤快捷鍵",
  "shortcuts.layout": "版面（先選擇 2 個以上節點）",
  "shortcuts.addNodes": "新增節點",
  "shortcuts.runWorkflow": "執行工作流程",
  "shortcuts.copyNodes": "複製選中節點",
  "shortcuts.paste": "貼上節點 / 圖片 / 文字",
  "shortcuts.undo": "復原",
  "shortcuts.redo": "重做",
  "shortcuts.addPrompt": "新增提示詞節點",
  "shortcuts.addImage": "新增圖片輸入節點",
  "shortcuts.addGenerateImage": "新增圖片生成節點",
  "shortcuts.addGenerateVideo": "新增影片生成節點",
  "shortcuts.addAudio": "新增音訊節點",
  "shortcuts.addVideoInput": "新增影片輸入節點",
  "shortcuts.delete": "刪除選中節點",
  "project.newProject": "新增專案",
  "project.settings": "專案設定",
  "project.tabProject": "專案",
  "project.tabProviders": "服務商",
  "project.tabNodeDefaults": "節點預設值",
  "project.name": "專案名稱",
  "project.directory": "專案目錄",
  "project.localFolderPlaceholder": "選擇這台裝置上的本機資料夾",
  "project.directoryHelp": "工作流程檔案和圖片會儲存在這裡，並自動建立 inputs 與 generations 子資料夾。",
  "project.embedImages": "將圖片嵌入為 base64",
  "project.inlineSettings": "在節點上顯示模型設定",
  "project.newapiwgDesc": "Popi 統一模型閘道",
  "project.baseUrl": "Base URL",
  "project.defaultLLM": "預設 LLM 設定",
  "project.systemLLMDefault": "使用系統預設值（Popi Models Doubao Seed 2.0 Lite）",
  "project.executionSettings": "執行設定",
  "project.maxParallel": "最大平行呼叫：{count}",
  "project.canvasHelp": "設定畫布導覽和互動方式。",
  "project.canvasAssistant": "畫布助手",
  "project.canvasAssistantShort": "助手",
  "project.panMode": "平移模式",
  "project.zoomMode": "縮放模式",
  "project.selectionMode": "選擇模式",
  "project.validating": "驗證中...",
  "project.errorChooseLocalFolder": "請從瀏覽器選擇本機資料夾，這樣專案會儲存在目前操作者的裝置上。",
  "modelSearch.search": "搜尋模型...",
  "modelSearch.allProviders": "所有服務商",
  "modelSearch.all": "全部",
  "modelSearch.allTypes": "所有類型",
  "modelSearch.image": "圖片",
  "modelSearch.video": "影片",
  "modelSearch.audio": "音訊",
  "modelSearch.refresh": "重新整理模型和參數",
  "modelSearch.loading": "正在載入模型...",
  "modelSearch.tryAgain": "重試",
  "modelSearch.removeFallback": "移除備用模型",
  "modelSearch.clearSelection": "清除目前選擇",
  "modelSearch.noModels": "找不到模型",
  "modelSearch.adjustFilters": "嘗試調整搜尋或篩選條件",
  "modelSearch.recent": "最近使用",
  "promptWorkflow.title": "用提示生成工作流程",
  "promptWorkflow.describe": "描述你的工作流程",
  "promptWorkflow.placeholder": "例如：基於參考圖建立光線和風格一致的產品攝影...",
  "promptWorkflow.help": "描述你希望工作流程完成什麼。請具體說明輸入、輸出和任何轉換步驟。",
  "promptWorkflow.note": "注意：此功能目前僅適用於 Gemini 模型。",
  "promptWorkflow.generating": "生成中...",
  "promptWorkflow.generate": "生成工作流程",
  "promptWorkflow.dismiss": "關閉提示",
  "promptWorkflow.validation": "請描述你的工作流程（至少 3 個字元）",
  "promptWorkflow.failed": "生成工作流程失敗",
  "workflowBrowser.noWorkflows": "找不到工作流程",
  "workflowBrowser.noWorkflowsDesc": "此資料夾不包含任何工作流程專案",
  "multiSelect.stackHorizontal": "水平排列 (H)",
  "multiSelect.stackVertical": "垂直排列 (V)",
  "multiSelect.arrangeGrid": "排列為網格 (G)",
  "multiSelect.removeGroup": "從群組中移除",
  "multiSelect.createGroup": "建立群組",
  "multiSelect.downloadZip": "下載圖片 ZIP",
  "cost.viewDetails": "查看費用詳情",
};

const ja: Dictionary = {
  ...en,
  "language.label": "言語",
  "common.back": "戻る",
  "common.next": "次へ",
  "common.cancel": "キャンセル",
  "common.close": "閉じる",
  "common.save": "保存",
  "common.create": "作成",
  "common.browse": "参照",
  "common.change": "変更",
  "common.clear": "クリア",
  "common.select": "選択",
  "common.selectModel": "モデルを選択",
  "common.skip": "スキップ",
  "common.show": "表示",
  "common.hide": "非表示",
  "common.override": "上書き",
  "common.configuredViaEnv": ".env で設定済み",
  "common.recommended": "推奨",
  "common.optional": "任意",
  "common.required": "必須",
  "common.download": "ダウンロード",
  "common.remove": "削除",
  "common.disconnect": "接続解除",
  "common.play": "再生",
  "common.pause": "一時停止",
  "common.set": "設定",
  "common.previous": "前へ",
  "common.nextItem": "次へ",
  "common.processing": "処理中...",
  "error.providerOverloaded": "{model}: プロバイダーが一時的に混雑しています。少し待って再試行するか、別のモデルに切り替えてください。",
  "node.optionalTitle": "この入力は任意です。空の入力はスキップされます",
  "node.markOptionalTitle": "任意に設定します。空の入力はこの分岐をスキップします",
  "header.openWelcome": "ウェルカム画面を開く",
  "header.untitled": "無題",
  "header.saving": "保存中...",
  "header.savedAt": "{time} に保存済み",
  "header.notSaved": "未保存",
  "header.saveProject": "プロジェクトを保存",
  "header.configureSaveLocation": "保存先を設定",
  "header.openProject": "プロジェクトを開く",
  "header.openProjectFolder": "プロジェクトフォルダを開く",
  "header.projectSettings": "プロジェクト設定",
  "header.revertAIChanges": "AI の変更を戻す",
  "header.revertAIChangesTitle": "AI 変更前のワークフローに戻す",
  "header.revertConfirm": "よろしいですか？以前のワークフローに戻します。",
  "header.shortcuts": "キーボードショートカット (?)",
  "header.support": "サポート",
  "header.commentsTitle": "未読コメント {unviewed} 件（合計 {total} 件）",
  "quickstart.description": "生成 AI パイプライン向けのノードベースのワークフローエディタです。ノードを接続して、画像、動画、音声、3D アセットを生成・変換するパイプラインを構築できます。",
  "quickstart.docs": "ドキュメント",
  "quickstart.waitlist": "NB Pro ウェイトリスト",
  "quickstart.newProject": "新規プロジェクト",
  "quickstart.newProjectDesc": "新しいワークフローを開始",
  "quickstart.loadWorkflow": "ワークフローを読み込み",
  "quickstart.loadWorkflowDesc": "既存ファイルを開く",
  "quickstart.templates": "テンプレート",
  "quickstart.templatesDesc": "事前作成済みワークフロー",
  "quickstart.promptWorkflow": "プロンプトで作成",
  "quickstart.promptWorkflowDesc": "Gemini に構築してもらう",
  "quickstart.beta": "ベータ",
  "quickstart.templateExplorerTitle": "テンプレート一覧",
  "quickstart.workflowTemplatesTitle": "ワークフローテンプレート",
  "quickstart.templatesIntro": "すぐに始められる事前作成済みワークフローです。テンプレートを選ぶとキャンバスに読み込まれます。",
  "quickstart.quickStart": "クイックスタート",
  "quickstart.searchTemplates": "テンプレートを検索",
  "quickstart.searchTemplatesPlaceholder": "テンプレートを検索...",
  "quickstart.category": "カテゴリ",
  "quickstart.provider": "プロバイダー",
  "quickstart.filterAll": "すべて",
  "quickstart.categorySimple": "シンプル",
  "quickstart.categoryAdvanced": "高度",
  "quickstart.clearFilters": "フィルターをクリア",
  "quickstart.clearAllFilters": "すべてのフィルターをクリア",
  "quickstart.noTemplatesMatch": "条件に一致するテンプレートがありません",
  "quickstart.adjustSearchFilters": "検索条件またはフィルターを調整してください",
  "quickstart.useWorkflow": "ワークフローを使用",
  "quickstart.loading": "読み込み中...",
  "quickstart.nodeCount": "{count} ノード",
  "quickstart.dismiss": "閉じる",
  "quickstart.loadTemplateFailed": "テンプレートの読み込みに失敗しました",
  "quickstart.template.productShot.name": "商品撮影",
  "quickstart.template.productShot.description": "商品を新しいシーンや環境に配置",
  "quickstart.template.modelProduct.name": "モデル + 商品",
  "quickstart.template.modelProduct.description": "モデル、商品、シーンを組み合わせる",
  "quickstart.template.colorVariations.name": "カラーバリエーション",
  "quickstart.template.colorVariations.description": "参照画像から商品の色違いを生成",
  "quickstart.template.backgroundSwap.name": "背景差し替え",
  "quickstart.template.backgroundSwap.description": "被写体を新しい背景に配置",
  "quickstart.template.styleTransfer.name": "スタイル変換",
  "quickstart.template.styleTransfer.description": "ある画像のスタイルを別の画像に適用",
  "quickstart.template.sceneComposite.name": "シーン合成",
  "quickstart.template.sceneComposite.description": "複数のシーンの要素を組み合わせる",
  "ftux.welcomeTo": "popiart-node へようこそ",
  "ftux.stepWelcome": "ようこそ",
  "ftux.stepApiKeys": "API キー",
  "ftux.stepModelDefaults": "モデル既定値",
  "ftux.stepReady": "準備完了",
  "ftux.getStarted": "開始",
  "ftux.skipSetup": "セットアップをスキップしますか？",
  "ftux.skipSetupDesc": "API キーとモデル既定値は後で設定から変更できます。",
  "ftux.welcomeTitle": "始めましょう。",
  "ftux.welcomeDesc": "AI モデルをブロックのように接続して、画像や動画などを生成します。",
  "ftux.apiKeysTitle": "API キー",
  "ftux.apiKeysDesc": "AI プロバイダーを使うためのキーをここに追加します（ブラウザに保存）。より安全に保持するには .env ファイルに保存してください。",
  "ftux.getApiKey": "{provider} の API キーを取得",
  "ftux.getApiKeyTip": "API キーを取得",
  "ftux.llmOnlyTip": "LLM ノードのみで使用",
  "ftux.enterKey": "キーを入力...",
  "ftux.modelsTitle": "モデルを選択",
  "ftux.modelsDesc": "画像と動画の既定 AI モデルを選びます。後から変更できます。",
  "ftux.defaultImageModel": "既定の画像モデル",
  "ftux.defaultVideoModel": "既定の動画モデル",
  "ftux.noneSet": "未設定",
  "ftux.readyTitle": "準備できました！",
  "ftux.readyDesc": "クイックチュートリアルを見ますか？",
  "ftux.startTutorial": "チュートリアルを開始",
  "ftux.tutorial.welcome": "まず基本操作を見ていきましょう。",
  "ftux.tutorial.addImage": "「画像」ボタンをクリックして画像ノードを追加します。",
  "ftux.tutorial.explainNode": "これはノードです。各ノードには決まった役割があります。\n\nこのノードは画像を追加します。",
  "ftux.tutorial.explainNodeInputs": "入力端子は常にノードの左側にあります。",
  "ftux.tutorial.explainNodeOutputs": "出力端子は常にノードの右側にあります。",
  "ftux.tutorial.dragAndDrop": "出力端子からドラッグして、キャンバスの空いている場所にドロップします。",
  "ftux.tutorial.selectGenerateImage": "このメニューには、このノードから接続できる項目が表示されます。\n\n「画像生成」を選択して AI 画像生成ノードを追加します。",
  "ftux.tutorial.addPromptNode": "「プロンプト」ボタンをクリックしてプロンプトノードを追加します。",
  "ftux.tutorial.connectPromptToGenerate": "プロンプトノードの出力端子から、画像生成ノードの入力端子へドラッグします。",
  "ftux.tutorial.populateContent": "サンプル内容を少し追加します 🎨",
  "ftux.tutorial.explainGenerateNode": "これは画像生成ノードです。プロンプトと参照画像をもとに、AI が画像を作成または編集します。",
  "ftux.tutorial.explainRunButton": "この「実行」ボタンをクリックするとワークフローが実行されます。Cmd+Enter（Windows では Ctrl+Enter）でも実行できます。",
  "ftux.tutorial.explainRunOptions": "ドロップダウンから、特定のノードから実行したり、選択中のノードだけを実行したりできます。",
  "ftux.tutorial.runWorkflow": "ワークフローを実行しましょう！「実行」ボタンをクリックして画像を生成します。",
  "ftux.tutorial.demonstrateDownstream": "次に、広げられる可能性を見てみましょう... 🎬✨",
  "ftux.tutorial.demonstrateComplete": "下流にさらにノードを接続して生成パイプラインを作ることも、無限のクリエイティブキャンバスとして使うこともできます。",
  "ftux.tutorial.saveProject": "プロジェクトを保存すると、作業内容と生成結果をローカルに残せます。",
  "ftux.tutorial.resources": "ヘルプやアイデアには以下のリソースをご覧ください：",
  "ftux.tutorial.discord": "Discord コミュニティに参加",
  "ftux.tutorial.complete": "準備完了です！制作を始めましょう。",
  "ftux.tutorial.clickToContinue": "任意の場所をクリックして続行",
  "ftux.tutorial.skip": "チュートリアルをスキップ",
  "toolbar.categoryInput": "入力",
  "toolbar.categoryText": "テキスト",
  "toolbar.categoryGenerate": "生成",
  "toolbar.categoryProcess": "処理",
  "toolbar.categoryRoute": "ルート",
  "toolbar.categoryOutput": "出力",
  "toolbar.image": "画像",
  "toolbar.video": "動画",
  "toolbar.prompt": "プロンプト",
  "toolbar.generate": "生成",
  "toolbar.output": "出力",
  "toolbar.allNodes": "すべてのノード",
  "toolbar.allModels": "すべてのモデル",
  "toolbar.browseModels": "モデルを参照",
  "toolbar.switchConnectors": "{style}コネクタに切り替え",
  "toolbar.curved": "曲線",
  "toolbar.angular": "直角",
  "toolbar.run": "実行",
  "toolbar.stop": "停止",
  "toolbar.running": "実行中...",
  "toolbar.runningNode": "{node} を実行中...",
  "toolbar.runningNodes": "{count} 個のノードを実行中...",
  "toolbar.nodesCount": "{count} 個のノード",
  "toolbar.runOptions": "実行オプション",
  "toolbar.runEntire": "ワークフロー全体を実行",
  "toolbar.runFromSelected": "選択ノードから実行",
  "toolbar.runSelectedOnly": "選択ノードのみ実行",
  "toolbar.runSelectedNodes": "選択ノードを実行",
  "toolbar.runSelectedNodesCount": "選択した {count} ノードを実行",
  "toolbar.selectSingleNode": "先に 1 つのノードを選択してください",
  "toolbar.selectOneOrMoreNodes": "先に 1 つ以上のノードを選択してください",
  "node.imageInput": "画像入力",
  "node.audioInput": "音声入力",
  "node.videoInput": "動画入力",
  "node.glbViewer": "3D ビューア",
  "node.prompt": "プロンプト",
  "node.promptConstructor": "プロンプト構築",
  "node.array": "配列",
  "node.generateImage": "画像生成",
  "node.generateVideo": "動画生成",
  "node.generate3d": "3D 生成",
  "node.generateAudio": "音声生成",
  "node.llmGenerate": "LLM 生成",
  "node.annotation": "注釈",
  "annotation.tool.select": "選択",
  "annotation.tool.rectangle": "矩形",
  "annotation.tool.circle": "円形",
  "annotation.tool.arrow": "矢印",
  "annotation.tool.draw": "描画",
  "annotation.tool.text": "テキスト",
  "annotation.undo": "元に戻す",
  "annotation.redo": "やり直し",
  "annotation.clear": "クリア",
  "annotation.cancel": "キャンセル",
  "annotation.done": "完了",
  "annotation.color": "色",
  "annotation.size": "サイズ",
  "annotation.fill": "塗りつぶし",
  "node.splitGrid": "グリッド分割",
  "node.videoStitch": "動画結合",
  "node.videoTrim": "動画トリム",
  "node.easeCurve": "イーズ曲線",
  "node.frameGrab": "フレーム取得",
  "node.imageCompare": "画像比較",
  "imageCompare.connectTwo": "比較する画像を 2 枚接続してください",
  "imageCompare.connectAnother": "比較するもう 1 枚の画像を接続してください",
  "imageCompare.imageAConnected": "画像 A が接続済み",
  "imageCompare.imageBConnected": "画像 B が接続済み",
  "imageCompare.imageAAlt": "画像 A",
  "imageCompare.imageBAlt": "画像 B",
  "upload.imageUnsupported": "未対応の形式です。PNG、JPG、WebP を使用してください。",
  "upload.imageTooLarge": "画像が大きすぎます。最大サイズは 10MB です。",
  "upload.videoUnsupported": "未対応の形式です。MP4、WebM、QuickTime 動画ファイルを使用してください。",
  "upload.videoTooLarge": "動画ファイルが大きすぎます。最大サイズは 200MB です。",
  "upload.audioUnsupported": "未対応の形式です。MP3、WAV、OGG、AAC、またはその他の音声形式を使用してください。",
  "upload.audioTooLarge": "音声ファイルが大きすぎます。最大サイズは 50MB です。",
  "imageInput.uploadedAlt": "アップロード済み画像",
  "imageInput.download": "画像をダウンロード",
  "imageInput.remove": "画像を削除",
  "videoInput.upload": "動画ファイルをアップロード",
  "videoInput.dropVideo": "動画をドロップまたはクリック",
  "videoInput.download": "動画をダウンロード",
  "videoInput.remove": "動画を削除",
  "audioInput.upload": "音声ファイルをアップロード",
  "audioInput.dropAudio": "音声をドロップまたはクリック",
  "audioInput.download": "音声をダウンロード",
  "audioInput.remove": "音声を削除",
  "audioInput.clear": "音声をクリア",
  "audioInput.loadingWaveform": "波形を読み込み中...",
  "generateAudio.fallbackUsed": "フォールバックを使用",
  "generateAudio.fallbackUsedTitle": "メインが失敗: {error}\n使用したフォールバック: {model}",
  "generateAudio.generating": "音声を生成中...",
  "annotation.noImage": "利用可能な画像がありません。画像を接続するか手動で読み込んでください。",
  "annotation.annotatedAlt": "注釈付き画像",
  "annotation.downloadImage": "画像をダウンロード",
  "annotation.removeImage": "画像を削除",
  "annotation.editCount": "編集（{count}）",
  "annotation.addAnnotations": "注釈を追加",
  "annotation.dropClickConnect": "ドロップ、クリック、または接続",
  "array.split": "分割",
  "array.delimiter": "区切り文字",
  "array.newline": "改行",
  "array.regexAdvanced": "正規表現（詳細）",
  "array.batch": "一括",
  "array.batchModeTitle": "一括モード：すべての項目を 1 つの下流ノードへ送信",
  "array.enableBatchMode": "一括モードを有効化",
  "array.autoRouteToPrompts": "プロンプトへ自動ルート",
  "array.by": "方法",
  "array.advanced": "詳細",
  "array.trim": "空白を削除",
  "array.removeEmpty": "空項目を削除",
  "array.parsedItems": "解析済み項目（{count}）",
  "array.noItemsParsed": "解析済み項目はありません",
  "array.selectedForNextConnection": "次の接続に選択済み（クリックで解除）",
  "array.selectForNextConnection": "クリックして次の接続に選択",
  "array.batchFooter": "一括：すべての項目を下流ノードへ送信",
  "array.nextWireUsesItem": "次のワイヤーは項目 {index} を使用",
  "array.noSelection": "未選択：ワイヤーは項目 1 から順に使用",
  "videoCommon.encoderUnsupported": "このブラウザは動画エンコードに対応していません。",
  "videoCommon.encoderHelp": "問題がある場合は Discord で Willie に連絡してください。",
  "videoCommon.checkingEncoder": "エンコーダーを確認中...",
  "videoCommon.processingPercent": "処理中... {progress}%",
  "videoTrim.videoIn": "動画入力",
  "videoTrim.videoOut": "動画出力",
  "videoTrim.connectVideo": "トリムする動画を接続してください",
  "videoTrim.source": "元動画",
  "videoTrim.trimmed": "トリム済み",
  "videoTrim.clearTrimmedVideo": "トリム済み動画をクリア",
  "videoTrim.start": "開始",
  "videoTrim.end": "終了",
  "videoTrim.duration": "長さ",
  "videoTrim.trimStartTime": "トリム開始時間",
  "videoTrim.trimEndTime": "トリム終了時間",
  "videoTrim.setStartToCurrent": "開始時間を現在の再生位置に設定",
  "videoTrim.setEndToCurrent": "終了時間を現在の再生位置に設定",
  "videoTrim.trim": "トリム",
  "videoStitch.videoInput": "動画 {index}",
  "videoStitch.audioInput": "音声",
  "videoStitch.output": "出力",
  "videoStitch.connectVideos": "結合する動画を接続してください",
  "videoStitch.clipAlt": "クリップ {id}",
  "videoStitch.clearVideo": "動画をクリア",
  "videoStitch.loop": "ループ",
  "videoStitch.stitch": "結合",
  "multiAngle.editorTitle": "マルチアングルエディタ",
  "multiAngle.imageEditorTitle": "画像マルチアングルエディタ",
  "multiAngle.imageTitle": "画像マルチアングル",
  "multiAngle.closeEditor": "マルチアングルエディタを閉じる",
  "multiAngle.generate": "マルチアングルを生成",
  "multiAngle.noImageToast": "利用可能な画像が見つかりません。マルチアングル画像を生成できません。",
  "multiAngle.sightControl": "マルチアングル視線コントロール",
  "multiAngle.noImage": "画像なし",
  "multiAngle.horizontal": "水平オービット",
  "multiAngle.vertical": "垂直ピッチ",
  "multiAngle.zoom": "ショットスケール",
  "multiAngle.prompt": "プロンプト",
  "multiAngle.enableCustomPrompt": "カスタムプロンプトを有効化",
  "multiAngle.promptDialog": "マルチアングルプロンプト",
  "multiAngle.promptInput": "マルチアングルプロンプト入力",
  "multiAngle.promptPlaceholder": "プロンプトを入力...",
  "multiAngle.reset": "パラメータをリセット",
  "multiAngle.resetAria": "マルチアングルパラメータをリセット",
  "multiAngle.adjustVerticalUp": "垂直ピッチを上げる",
  "multiAngle.adjustVerticalDown": "垂直ピッチを下げる",
  "multiAngle.adjustHorizontalLeft": "水平オービットを左へ調整",
  "multiAngle.adjustHorizontalRight": "水平オービットを右へ調整",
  "multiAngle.preset.custom": "カスタム",
  "multiAngle.preset.fisheye": "魚眼",
  "multiAngle.preset.tilt": "傾き",
  "multiAngle.preset.frontOverhead": "正面俯瞰",
  "multiAngle.preset.frontLow": "正面ローアングル",
  "multiAngle.preset.panoramicOverhead": "パノラマ俯瞰",
  "multiAngle.preset.back": "背面",
  "multiAngle.zoom.wide": "ワイド",
  "multiAngle.zoom.medium": "ミディアム",
  "multiAngle.zoom.close": "クローズアップ",
  "workflow.splitGrid.noImage": "分割できる画像がありません。先に画像を生成または読み込んでください。",
  "workflow.splitGrid.detectFailed": "画像内のグリッドを検出できませんでした。",
  "workflow.splitGrid.failed": "画像グリッドの分割に失敗しました: {error}",
  "workflow.generatedSuccess": "ワークフローを生成しました",
  "workflow.generatedFailed": "ワークフローの生成に失敗しました",
  "workflow.generatedFailedRetry": "ワークフローの生成に失敗しました。再試行してください。",
  "workflow.appliedEdits": "{count} 件の編集を適用しました",
  "chat.title": "キャンバスアシスタント",
  "chat.beta": "ベータ",
  "chat.clear": "チャットをクリア",
  "chat.close": "チャットを閉じる",
  "chat.dismiss": "閉じる",
  "chat.emptyTitle": "このキャンバスについて質問したり、編集案を作成できます。",
  "chat.diagnoseSelected": "選択を診断",
  "chat.makeVariants": "バリエーション作成",
  "chat.promptDiagnose": "選択したノードを診断し、最小限の有用な修正を提案してください。",
  "chat.promptVariants": "選択した画像または生成ノードから、4つの視覚バリエーションを作る編集案を準備してください。",
  "chat.proposalReady": "提案を準備しました",
  "chat.candidateReady": "候補ワークフローを適用できます。",
  "chat.runReady": "実行アクションを適用できます。",
  "chat.candidateWorkflow": "候補ワークフロー",
  "chat.applyWorkflow": "ワークフローを適用",
  "chat.operationPreview": "操作プレビュー",
  "chat.applyEdits": "編集を適用",
  "chat.runPreview": "実行プレビュー",
  "chat.runNow": "今すぐ実行",
  "chat.focusedNodes": "選択中の {count} ノードにフォーカス",
  "chat.runSelected": "選択を実行",
  "chat.runAffected": "影響範囲を実行（{count}）",
  "chat.placeholder": "メッセージを入力...",
  "chat.building": "構築中...",
  "chat.buildWorkflow": "ワークフローを構築",
  "chat.open": "アシスタント",
  "chat.noSelection": "先に1つ以上のノードを選択してください。",
  "workflow.invalidWorkflowFile": "ワークフローファイル形式が無効です",
  "workflow.parseWorkflowFileFailed": "ワークフローファイルの解析に失敗しました",
  "splitGridNode.sourceGridAlt": "元グリッド",
  "splitGridNode.connectImage": "画像を接続",
  "splitGridNode.gridSummary": "{rows}x{cols} グリッド（{count} 枚の画像）",
  "splitGridNode.generateSetsCreated": "{count} 個の生成セットを作成済み",
  "splitGridNode.notConfigured": "未設定 - 設定をクリック",
  "splitGridNode.configureFirst": "先にノードを設定してください",
  "splitGridNode.connectImageFirst": "先に画像を接続してください",
  "splitGridNode.splitGrid": "グリッドを分割",
  "splitGridNode.split": "分割",
  "node.router": "ルーター",
  "node.switch": "スイッチ",
  "node.conditionalSwitch": "条件スイッチ",
  "node.output": "出力",
  "node.outputGallery": "出力ギャラリー",
  "output.downloadCurrent": "現在の{type}をダウンロード",
  "output.downloadAll": "すべての出力をダウンロード",
  "node.settings": "設定",
  "node.runToGenerate": "実行して生成",
  "node.selectModel": "モデルを選択...",
  "node.primary": "メイン",
  "node.text": "テキスト",
  "node.image": "画像",
  "node.notUsedByModel": "このモデルでは未使用",
  "node.generationFailed": "生成に失敗しました",
  "node.videoGenerationFailed": "動画生成に失敗しました",
  "node.loadingParameters": "パラメータを読み込み中...",
  "node.noParameters": "利用可能なパラメータはありません",
  "node.default": "既定",
  "node.model": "モデル",
  "node.size": "サイズ",
  "node.quality": "品質",
  "node.voice": "音声",
  "node.sound": "音声",
  "node.duration": "長さ",
  "node.seconds": "{count}秒",
  "node.aspectRatio": "アスペクト比",
  "node.resolution": "解像度",
  "node.googleSearch": "Google 検索",
  "node.imageSearch": "画像検索",
  "node.provider": "プロバイダー",
  "node.temperature": "温度：{value}",
  "node.maxTokens": "最大トークン：{value}",
  "node.generic": "ノード",
  "splitGridSettings.title": "グリッド分割設定",
  "splitGridSettings.gridLayout": "グリッドレイアウト",
  "splitGridSettings.gridSummary": "グリッドは {rows}x{cols} = {count} 枚の画像に分割されます",
  "splitGridSettings.defaultPrompt": "既定のプロンプト",
  "splitGridSettings.promptPlaceholder": "生成されるすべての画像に適用するプロンプトを入力...",
  "splitGridSettings.promptHelp": "作成後、各プロンプトノードは個別に編集できます",
  "splitGridSettings.generateNodeSettings": "生成ノード設定",
  "splitGridSettings.createGenerateSets": "{count} 個の生成セットを作成",
  "imageInput.upload": "画像をアップロード",
  "imageInput.dropImage": "画像をドロップ",
  "dropOverlay.workflow": "ドロップしてワークフローを読み込み",
  "dropOverlay.node": "ドロップしてノードを作成",
  "dropOverlay.audio": "音声をドロップしてノードを作成",
  "dropOverlay.image": "画像をドロップしてノードを作成",
  "dropOverlay.splittingImageGrid": "画像グリッドを分割中...",
  "connectionMenu.addNode": "{type} ノードを追加",
  "connectionMenu.type.image": "画像",
  "connectionMenu.type.text": "テキスト",
  "connectionMenu.type.audio": "音声",
  "connectionMenu.type.video": "動画",
  "connectionMenu.type.3d": "3D",
  "connectionMenu.type.easeCurve": "設定",
  "connectionMenu.splitGridNode": "グリッド分割ノード",
  "connectionMenu.splitGridNow": "今すぐグリッド分割",
  "connectionMenu.multiAngleImage": "画像マルチアングル",
  "connectionMenu.navigate": "移動",
  "connectionMenu.select": "選択",
  "prompt.placeholder": "生成したい内容を入力...",
  "prompt.connectedPlaceholder": "接続ノードからのテキスト（編集可）...",
  "prompt.optionalPlaceholder": "任意のプロンプト（空ならスキップ）...",
  "prompt.addVariable": "変数を追加",
  "prompt.setVariableName": "変数名を設定",
  "prompt.variableHelp": "このプロンプトを PromptConstructor ノードの変数として使用します",
  "prompt.variableName": "変数名",
  "prompt.variablePlaceholder": "例: color, style, subject",
  "prompt.preview": "プレビュー：{value}",
  "composer.promptProvidedByUpstream": "プロンプトは上流のテキストノードから提供されています。",
  "composer.videoStitchClipCount": "{count} 本の動画クリップ",
  "composer.videoStitchAudio": "音声接続済み",
  "composer.videoStitchNoAudio": "音声なし",
  "composer.videoStitchInputs": "動画入力",
  "composer.videoStitchInputHint": "クリップ順序はノード上で調整",
  "composer.videoStitchNeedTwo": "結合には動画クリップを 2 本以上接続してください。",
  "composer.videoStitchClip": "クリップ {index}",
  "composer.videoStitchLoop": "ループ",
  "composer.videoStitchOutputReady": "出力準備完了",
  "composer.videoStitchNoOutput": "出力はまだありません",
  "composer.videoStitchReady": "現在のノードを結合できます",
  "composer.videoStitchRun": "現在の動画ノードを結合",
  "composer.easeCurveVideoConnected": "動画接続済み",
  "composer.easeCurveNoVideo": "緩動曲線を適用するには動画入力を接続してください。",
  "composer.easeCurveInherited": "継承設定",
  "composer.easeCurveLocal": "ローカル設定",
  "composer.easeCurveSettings": "曲線設定",
  "composer.easeCurvePreset": "プリセット",
  "composer.easeCurveCustom": "カスタムベジェ",
  "composer.easeCurveHandles": "ベジェ制御点",
  "composer.easeCurveInput": "入力",
  "composer.easeCurveDuration": "出力時間",
  "composer.easeCurveOutputReady": "出力準備完了",
  "composer.easeCurveNoOutput": "出力はまだありません",
  "composer.easeCurveReady": "現在の曲線を適用できます",
  "composer.easeCurveRun": "現在の緩動曲線ノードを適用",
  "shortcuts.title": "キーボードショートカット",
  "shortcuts.general": "一般",
  "shortcuts.addNodes": "ノード追加",
  "shortcuts.layout": "レイアウト（先に 2 つ以上のノードを選択）",
  "shortcuts.canvas": "キャンバス",
  "shortcuts.runWorkflow": "ワークフローを実行",
  "shortcuts.copyNodes": "選択ノードをコピー",
  "shortcuts.paste": "ノード / 画像 / テキストを貼り付け",
  "shortcuts.undo": "元に戻す",
  "shortcuts.redo": "やり直し",
  "shortcuts.showShortcuts": "ショートカットを表示",
  "shortcuts.addPrompt": "プロンプトノードを追加",
  "shortcuts.addImage": "画像入力ノードを追加",
  "shortcuts.addGenerateImage": "画像生成ノードを追加",
  "shortcuts.addGenerateVideo": "動画生成ノードを追加",
  "shortcuts.addLLM": "LLM テキストノードを追加",
  "shortcuts.addAnnotation": "注釈ノードを追加",
  "shortcuts.addAudio": "音声ノードを追加",
  "shortcuts.addVideoInput": "動画入力ノードを追加",
  "shortcuts.addArray": "配列ノードを追加",
  "shortcuts.stackVertical": "選択ノードを縦に並べる",
  "shortcuts.stackHorizontal": "選択ノードを横に並べる",
  "shortcuts.arrangeGrid": "選択ノードをグリッド配置",
  "shortcuts.zoom": "ズームイン / アウト",
  "shortcuts.pan": "パン（macOS）",
  "shortcuts.delete": "選択ノードを削除",
  "project.newProject": "新規プロジェクト",
  "project.settings": "プロジェクト設定",
  "project.tabProject": "プロジェクト",
  "project.tabProviders": "プロバイダー",
  "project.tabNodeDefaults": "ノード既定値",
  "project.tabCanvas": "キャンバス",
  "project.name": "プロジェクト名",
  "project.directory": "プロジェクトディレクトリ",
  "project.localFolderPlaceholder": "このデバイス上のローカルフォルダを選択",
  "project.directoryHelp": "ワークフローファイルと画像はここに保存されます。inputs と generations のサブフォルダは自動作成されます。",
  "project.embedImages": "画像を base64 として埋め込む",
  "project.embedImagesDesc": "すべての画像をワークフローに埋め込みます。ファイルは大きくなり、非常に大きなワークフローではメモリ制限に達する場合があります。",
  "project.inlineSettings": "ノード上にモデル設定を表示",
  "project.inlineSettingsDesc": "生成ノード内にモデルパラメータを表示し、サイドパネルを使いません",
  "project.envHelp": ".env.local に API キーを追加するとより安全です。ここで追加したキーは .env を上書きし、ブラウザに保存されます。",
  "project.newapiwgDesc": "Popi 統合モデルゲートウェイ",
  "project.baseUrl": "Base URL",
  "project.systemImageDefault": "システム既定（Popi Models）",
  "project.noneSetFirstUse": "未設定（初回使用時に選択）",
  "project.defaultLLM": "既定の LLM 設定",
  "project.systemLLMDefault": "システム既定値を使用（Popi Models Doubao Seed 2.0 Lite）",
  "project.provider": "プロバイダー",
  "project.model": "モデル",
  "project.temp": "温度: {value}",
  "project.tokens": "トークン: {value}",
  "project.executionSettings": "実行設定",
  "project.maxParallel": "最大並列呼び出し: {count}",
  "project.executionHelp": "ワークフロー実行時に並列実行するノード数の上限です。値を上げると速くなる場合がありますが、API レート制限のリスクも上がります。",
  "project.defaultsHelp": "これらの既定値はキーボードショートカット（Shift+G、Shift+L など）でノードを作成するときに適用されます。",
  "project.canvasHelp": "キャンバスの移動と操作方法を設定します。",
  "project.canvasAssistant": "キャンバスアシスタント",
  "project.canvasAssistantShort": "アシスタント",
  "project.panMode": "パンモード",
  "project.panSpace": "Space を押しながらドラッグしてパン",
  "project.panMiddle": "マウス中ボタンでクリックしてドラッグ",
  "project.panAlways": "キーを押さずにパン",
  "project.panSpaceLabel": "Space + ドラッグ",
  "project.panMiddleLabel": "中ボタン",
  "project.panAlwaysLabel": "常時オン",
  "project.zoomMode": "ズームモード",
  "project.zoomAlt": "Alt を押しながらスクロールでズーム",
  "project.zoomCtrl": "Ctrl/Cmd を押しながらスクロールでズーム",
  "project.zoomScroll": "修飾キーなしでスクロールズーム",
  "project.zoomAltLabel": "Alt + スクロール",
  "project.zoomCtrlLabel": "Ctrl + スクロール",
  "project.zoomScrollLabel": "スクロール",
  "project.selectionMode": "選択モード",
  "project.selectClick": "クリックしてノードを選択",
  "project.selectAlt": "Alt を押しながらドラッグして選択",
  "project.selectShift": "Shift を押しながらドラッグして選択",
  "project.selectClickLabel": "クリック",
  "project.selectAltLabel": "Alt + ドラッグ",
  "project.selectShiftLabel": "Shift + ドラッグ",
  "project.validating": "検証中...",
  "project.errorOpenDir": "ディレクトリ選択を開けませんでした",
  "project.errorOpenDirWithReason": "ディレクトリ選択を開けませんでした: {error}",
  "project.errorNameRequired": "プロジェクト名は必須です",
  "project.errorDirectoryRequired": "プロジェクトディレクトリは必須です",
  "project.errorChooseLocalFolder": "このデバイスに保存するため、ブラウザからローカルフォルダを選択してください。",
  "project.errorAbsolutePath": "プロジェクトディレクトリは絶対パスである必要があります（/、ドライブ文字、UNC パスで始まる形式）",
  "project.errorNotDirectory": "プロジェクトパスはディレクトリではありません",
  "project.errorValidateDir": "ディレクトリ検証に失敗しました: {error}",
  "modelSearch.search": "モデルを検索...",
  "modelSearch.allProviders": "すべてのプロバイダー",
  "modelSearch.all": "すべて",
  "modelSearch.allTypes": "すべての種類",
  "modelSearch.image": "画像",
  "modelSearch.video": "動画",
  "modelSearch.audio": "音声",
  "modelSearch.refresh": "モデルとスキーマを更新",
  "modelSearch.loading": "モデルを読み込み中...",
  "modelSearch.tryAgain": "再試行",
  "modelSearch.removeFallback": "フォールバックを削除",
  "modelSearch.clearSelection": "現在の選択をクリア",
  "modelSearch.noModels": "モデルが見つかりません",
  "modelSearch.adjustFilters": "検索条件またはフィルターを調整してください",
  "modelSearch.recent": "最近使用",
  "promptWorkflow.title": "プロンプトでワークフロー作成",
  "promptWorkflow.describe": "ワークフローを説明",
  "promptWorkflow.placeholder": "例: 参考画像から一貫した照明とスタイルの商品写真を作成...",
  "promptWorkflow.help": "ワークフローで実現したいことを説明してください。入力、出力、変換内容を具体的に書くと効果的です。",
  "promptWorkflow.note": "注: この機能は現在 Gemini モデルでのみ動作します。",
  "promptWorkflow.generating": "生成中...",
  "promptWorkflow.generate": "ワークフローを生成",
  "promptWorkflow.dismiss": "閉じる",
  "promptWorkflow.validation": "ワークフローを説明してください（3 文字以上）",
  "promptWorkflow.failed": "ワークフローの生成に失敗しました",
  "workflowBrowser.noWorkflows": "ワークフローが見つかりません",
  "workflowBrowser.noWorkflowsDesc": "このフォルダにはワークフロープロジェクトがありません",
  "workflowBrowser.title": "ワークフロー",
  "workflowBrowser.chooseFolderDesc": "ワークフロープロジェクトを含むフォルダを選択します。後で変更できます。",
  "workflowBrowser.chooseFolder": "フォルダを選択",
  "workflowBrowser.projectCount": "{count} 件のプロジェクト",
  "workflowBrowser.openFromDirectory": "ディレクトリから開く",
  "workflowBrowser.changeFolder": "フォルダを変更",
  "workflowBrowser.justNow": "たった今",
  "workflowBrowser.minutesAgo": "{count} 分前",
  "workflowBrowser.hoursAgo": "{count} 時間前",
  "workflowBrowser.daysAgo": "{count} 日前",
  "workflowBrowser.monthsAgo": "{count} か月前",
  "workflowBrowser.errorNoWorkflows": "このフォルダにワークフローが見つかりません",
  "workflowBrowser.errorList": "ワークフロー一覧の取得に失敗しました",
  "workflowBrowser.errorFetch": "ワークフローの取得に失敗しました",
  "multiSelect.stackHorizontal": "横に並べる (H)",
  "multiSelect.stackVertical": "縦に並べる (V)",
  "multiSelect.arrangeGrid": "グリッド配置 (G)",
  "multiSelect.removeGroup": "グループから削除",
  "multiSelect.createGroup": "グループを作成",
  "multiSelect.downloadZip": "画像を ZIP でダウンロード",
  "cost.viewDetails": "コスト詳細を表示",
};

const dictionaries: Record<Language, Dictionary> = {
  en,
  "zh-CN": zhCN,
  "zh-TW": zhTW,
  ja,
};

function detectLanguage(): Language {
  if (typeof navigator === "undefined") return "en";
  const language = navigator.language.toLowerCase();
  if (language.startsWith("ja")) return "ja";
  if (language.includes("tw") || language.includes("hk") || language.includes("hant")) return "zh-TW";
  if (language.startsWith("zh")) return "zh-CN";
  return "en";
}

function interpolate(text: string, values?: Record<string, string | number>) {
  if (!values) return text;
  return text.replace(/\{(\w+)\}/g, (_, key) => String(values[key] ?? `{${key}}`));
}

interface I18nContextValue {
  language: Language;
  setLanguage: (language: Language) => void;
  t: (key: TranslationKey, values?: Record<string, string | number>) => string;
}

const I18nContext = createContext<I18nContextValue | null>(null);

export function I18nProvider({ children }: { children: React.ReactNode }) {
  const [language, setLanguageState] = useState<Language>("en");

  useEffect(() => {
    const stored = window.localStorage.getItem(STORAGE_KEY) as Language | null;
    setLanguageState(stored && stored in dictionaries ? stored : detectLanguage());
  }, []);

  useEffect(() => {
    document.documentElement.lang = language;
    window.localStorage.setItem(STORAGE_KEY, language);
  }, [language]);

  const value = useMemo<I18nContextValue>(() => ({
    language,
    setLanguage: setLanguageState,
    t: (key, values) => interpolate(dictionaries[language][key] ?? en[key], values),
  }), [language]);

  return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>;
}

export function useI18n() {
  const context = useContext(I18nContext);
  return context ?? {
    language: "en",
    setLanguage: () => {},
    t: (key, values) => interpolate(en[key], values),
  };
}
