cozyclay 3.1.2 → 3.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.md +1 -1
- package/bin/agent/agent-runner.mjs +160 -151
- package/bin/agent/agent-stream.mjs +86 -0
- package/bin/agent/anthropic-subscription-events.mjs +120 -0
- package/bin/agent/anthropic-subscription-prompt.mjs +116 -0
- package/bin/agent/anthropic-subscription-stream.mjs +251 -0
- package/bin/agent/anthropic-subscription.mjs +73 -0
- package/bin/agent/claude-code-executable.mjs +93 -0
- package/bin/agent/cliproxy-provider.mjs +25 -11
- package/bin/agent/model-roles.mjs +112 -0
- package/bin/agent/openai-compatible-provider.mjs +95 -0
- package/bin/agent/pi-tools.mjs +19 -10
- package/bin/agent/provider-login.mjs +3 -1
- package/bin/agent/provider-routes.mjs +2 -0
- package/bin/agent/providers.mjs +35 -130
- package/dist/app/index.html +5 -5
- package/dist/assets/TaskBoard-U1Yl-4F1.js +1 -0
- package/dist/assets/app-BdRlMf4J.css +1 -0
- package/dist/assets/app-DwETmoQZ.js +5144 -0
- package/dist/assets/{motion3-compile-dgGhrvPK.js → motion3-compile-DDG3wklD.js} +1 -1
- package/dist/assets/scene-asset-sync-Bz59Ghdz.js +13 -0
- package/dist/assets/task-board-DFfNa0xg.js +15 -0
- package/dist/assets/{workflow-C87p6E1E.js → workflow-BMJGd_gZ.js} +1 -1
- package/dist/cozyclay-package.json +1 -1
- package/dist/workflow/index.html +3 -3
- package/package.json +6 -4
- package/src/App.jsx +28 -28
- package/src/agent-image-client.js +2 -2
- package/src/asset-pane.jsx +14 -14
- package/src/character-kind.js +13 -12
- package/src/commands/project.js +2 -1
- package/src/commands/shot.js +1 -1
- package/src/commands/view.js +2 -2
- package/src/domains/cast.js +15 -15
- package/src/domains/motion/motion-range-pin-rules.js +1 -1
- package/src/domains/motion.js +11 -11
- package/src/domains/objects.js +20 -30
- package/src/domains/scenes.js +14 -21
- package/src/domains/shots.js +3 -3
- package/src/first-success-guide.jsx +19 -3
- package/src/hierarchy-panel.jsx +6 -7
- package/src/locale-zh/app.js +124 -0
- package/src/locale-zh/domains.js +36 -0
- package/src/locale-zh/labels.js +148 -0
- package/src/locale-zh/panels.js +225 -0
- package/src/locale-zh/shell.js +230 -0
- package/src/locale-zh/timeline.js +145 -0
- package/src/locale-zh/workflow.js +104 -0
- package/src/locale-zh/workspace.js +190 -0
- package/src/locale-zh.js +21 -0
- package/src/locale.js +16 -6
- package/src/main.jsx +1 -1
- package/src/motion/beat-board-model.js +4 -4
- package/src/motion/beat-board.jsx +1 -1
- package/src/motion/ik-pick.js +24 -0
- package/src/motion/take-segments.jsx +5 -5
- package/src/motion/timeline.jsx +30 -40
- package/src/motion/waypoints.js +6 -6
- package/src/object-catalog.jsx +4 -5
- package/src/panels/CameraPanel.jsx +3 -3
- package/src/panels/HdrEnvironmentControls.jsx +2 -1
- package/src/panels/ObjectTransformPanel.jsx +19 -29
- package/src/panels/PoseImagePanel.jsx +8 -8
- package/src/panels/PosePanel.jsx +2 -2
- package/src/panels/ReferenceImageField.jsx +2 -2
- package/src/panels/RigControlPanel.jsx +2 -2
- package/src/panels/SculptPanel.jsx +9 -9
- package/src/panels/SubjectBox.jsx +3 -3
- package/src/panels/SubjectsPanel.jsx +1 -1
- package/src/posestudio.jsx +10 -8
- package/src/project-browser.css +7 -6
- package/src/project-browser.jsx +7 -6
- package/src/project-open-file.js +19 -0
- package/src/qa-label.js +1 -1
- package/src/range-pin-panel.jsx +14 -14
- package/src/resource-status.jsx +3 -3
- package/src/result-modal.jsx +10 -10
- package/src/settings-menu.jsx +4 -1
- package/src/shell/BoardSlot.jsx +5 -5
- package/src/shell/CompositionGuideButton.jsx +1 -1
- package/src/shell/GhostsMenuGroup.jsx +1 -1
- package/src/shell/StatusBar.jsx +4 -4
- package/src/shell/ViewportToolbar.jsx +3 -3
- package/src/shell/add-camera-shot.js +2 -2
- package/src/shell/details-selection.js +1 -1
- package/src/shell/preference-controls.jsx +2 -1
- package/src/shot-guides.js +5 -5
- package/src/stage-hdr.js +3 -3
- package/src/stage-labels-ko.js +2 -2
- package/src/stage-startup-defaults.js +3 -3
- package/src/studio-actions.js +2 -2
- package/src/styles/tokens.css +1 -1
- package/src/styles.css +1 -1
- package/src/use-case-question.jsx +18 -18
- package/src/workflow/AgentPanel.jsx +4 -4
- package/src/workflow/AgentPlanCard.jsx +1 -1
- package/src/workflow/AgentProgressCard.jsx +4 -4
- package/src/workflow/AgentTurnSteps.jsx +1 -1
- package/src/workflow/LoginPanel.jsx +1 -1
- package/src/workflow/TaskBoard.jsx +1 -1
- package/src/workflow/TaskInline.jsx +1 -1
- package/src/workflow/agent-client.js +1 -1
- package/src/workflow/agent-turn.js +5 -5
- package/src/workflow/task-board.js +4 -4
- package/tools/loc-baseline.json +1 -1
- package/tools/run-tests.mjs +4 -4
- package/dist/assets/TaskBoard-GR8xZm5c.js +0 -1
- package/dist/assets/app-Bhe0TpGz.css +0 -1
- package/dist/assets/app-C4QTcDcb.js +0 -5144
- package/dist/assets/scene-asset-sync-qoxcdrhj.js +0 -13
- package/dist/assets/task-board-BqReHzOi.js +0 -15
package/src/domains/motion.js
CHANGED
|
@@ -31,7 +31,7 @@ import * as THREE from "three";
|
|
|
31
31
|
import { StudioProtocolError } from "../studio-agent-protocol.js";
|
|
32
32
|
import { applyTakeFrame } from "../motion3/playback.js";
|
|
33
33
|
import { advancePlayback, installPlaybackApplier } from "./playback.js";
|
|
34
|
-
import {
|
|
34
|
+
import { ko } from "../locale.js";
|
|
35
35
|
import { isProxyFigure } from "../scenes.js";
|
|
36
36
|
import { refuseRigOnly } from "../character-kind.js";
|
|
37
37
|
import { DEFAULT_POSE, restoreBindPositions, applyPose, applyHipsOffset } from "../poses.js";
|
|
@@ -439,9 +439,9 @@ export function useMotion(appContext) {
|
|
|
439
439
|
ikPreDragRef.current = null;
|
|
440
440
|
if (pending.rig) appContext.shared.poseMemberAtFrame(pending.rig, motion, pending.state, appContext.shared.tlFrame, IK_CORRECTION_BLEND_FRAMES);
|
|
441
441
|
setIkTick((value) => value + 1);
|
|
442
|
-
appContext.notify(
|
|
443
|
-
|
|
444
|
-
|
|
442
|
+
appContext.notify(ko(`Applied IK correction across frames ${pending.startFrame}–${pending.endFrame}`,
|
|
443
|
+
`IK 보정을 ${pending.startFrame}–${pending.endFrame}프레임에 적용했어요`,
|
|
444
|
+
`已在第 ${pending.startFrame}–${pending.endFrame} 帧应用 IK 修正`));
|
|
445
445
|
return true;
|
|
446
446
|
}
|
|
447
447
|
|
|
@@ -496,8 +496,8 @@ export function useMotion(appContext) {
|
|
|
496
496
|
setRangePinSelection(resolved.pin.id);
|
|
497
497
|
setRangePinPreview({ draft: { ...draft, id: resolved.pin.id }, track: resolved.pin.track, target: resolved.targetWorld.toArray(), conflictFrames: [], overlapError: "" });
|
|
498
498
|
appContext.shared.setTlPlaying(false);
|
|
499
|
-
appContext.notify(
|
|
500
|
-
} catch (error) { appContext.notify(ko(`Could not apply pin — ${error.message}`, `고정을 적용하지 못했어요 — ${error.message}`)); }
|
|
499
|
+
appContext.notify(ko(`Applied ${resolved.pin.track} pin · frames ${resolved.pin.startFrame}–${resolved.pin.endFrame}`, `${resolved.pin.track} 고정을 적용했어요 · ${resolved.pin.startFrame}–${resolved.pin.endFrame}프레임`, `已应用 ${resolved.pin.track} 固定 · 第 ${resolved.pin.startFrame}–${resolved.pin.endFrame} 帧`));
|
|
500
|
+
} catch (error) { appContext.notify(ko(`Could not apply pin — ${error.message}`, `고정을 적용하지 못했어요 — ${error.message}`, `无法应用固定 — ${error.message}`)); }
|
|
501
501
|
}
|
|
502
502
|
function deleteRangePin(pinId) {
|
|
503
503
|
const state = ensureRangePinState(appContext.shared.ikStateRef.current), pin = state.pins.get(pinId);
|
|
@@ -512,7 +512,7 @@ export function useMotion(appContext) {
|
|
|
512
512
|
liveState.pinResiduals.delete(pinId);
|
|
513
513
|
liveState.tracked = new Set([...liveState.keys.values()].flatMap((entry) => [...entry.keys()]));
|
|
514
514
|
setRangePinSelection(null); setRangePinPreview(null); setIkTick((value) => value + 1);
|
|
515
|
-
appContext.notify(
|
|
515
|
+
appContext.notify(ko(`Deleted ${pin.track} pin`, `${pin.track} 고정을 삭제했어요`, `已删除 ${pin.track} 固定`));
|
|
516
516
|
}
|
|
517
517
|
|
|
518
518
|
/** Deep copy of an IK state's key map: frame → Map(trackId → {q,p}), with
|
|
@@ -881,7 +881,7 @@ export function useMotion(appContext) {
|
|
|
881
881
|
function ikAddKeyframe() {
|
|
882
882
|
if (!ikChains) return;
|
|
883
883
|
if (!keyIkPoseAtPlayhead()) return;
|
|
884
|
-
appContext.notify(
|
|
884
|
+
appContext.notify(ko(`Full-body IK key at frame ${appContext.shared.tlFrame}`, `${appContext.shared.tlFrame}프레임에 전신 IK 키를 추가했어요`, `已在第 ${appContext.shared.tlFrame} 帧添加全身 IK 关键帧`));
|
|
885
885
|
}
|
|
886
886
|
|
|
887
887
|
function ikDeleteKeyframe(frame) {
|
|
@@ -918,9 +918,9 @@ export function useMotion(appContext) {
|
|
|
918
918
|
// Handles re-seat on the posed effectors, ready to drag into a refinement.
|
|
919
919
|
ikSeedTargets(ikChains, appContext.shared.ikStateRef.current);
|
|
920
920
|
setIkTick((n) => n + 1);
|
|
921
|
-
appContext.notify(
|
|
922
|
-
|
|
923
|
-
|
|
921
|
+
appContext.notify(ko(`Pose keyed as a full-body IK correction at frame ${appContext.shared.tlFrame} — the take stays`,
|
|
922
|
+
`${appContext.shared.tlFrame}프레임에 포즈를 전신 IK 보정 키로 추가했어요 — 모션은 그대로예요`,
|
|
923
|
+
`已在第 ${appContext.shared.tlFrame} 帧将姿势记录为全身 IK 修正关键帧 — 动作保持不变`));
|
|
924
924
|
return true;
|
|
925
925
|
}
|
|
926
926
|
|
package/src/domains/objects.js
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
tidyAngle,
|
|
32
32
|
} from "../scene-objects.js";
|
|
33
33
|
|
|
34
|
-
import { ko
|
|
34
|
+
import { ko } from "../locale.js";
|
|
35
35
|
import {
|
|
36
36
|
sceneObjectNameDisplayKo,
|
|
37
37
|
attachWorldMatrix,
|
|
@@ -264,7 +264,7 @@ export function useObjects(appContext) {
|
|
|
264
264
|
return;
|
|
265
265
|
}
|
|
266
266
|
changeSceneObject(object.id, patch);
|
|
267
|
-
appContext.notify(
|
|
267
|
+
appContext.notify(ko(`${sceneObjectNameDisplayKo(object.name)} dropped to surface`, `${sceneObjectNameDisplayKo(object.name)}을 표면 위에 내려놓았어요`, `${sceneObjectNameDisplayKo(object.name)}已放到表面`));
|
|
268
268
|
}
|
|
269
269
|
|
|
270
270
|
// How much of the wall counts as the wall, and how wide the brush that
|
|
@@ -314,7 +314,7 @@ export function useObjects(appContext) {
|
|
|
314
314
|
// swallows the very next W/E/R. Renaming stays on F2/Return and the row's
|
|
315
315
|
// context menu. (docs/unity-reference.md §9.7)
|
|
316
316
|
setGizmoMode("move");
|
|
317
|
-
appContext.notify(
|
|
317
|
+
appContext.notify(ko(`${sceneObjectNameDisplayKo(object.name)} added — W move, E rotate, R scale`, `${sceneObjectNameDisplayKo(object.name)} 추가됨 — W 이동, E 회전, R 크기`, `${sceneObjectNameDisplayKo(object.name)}已添加 — W 移动,E 旋转,R 缩放`));
|
|
318
318
|
}
|
|
319
319
|
|
|
320
320
|
/** "Sofa 2.png" reads as a set piece; "sofa-2.png" does not. The extension
|
|
@@ -345,11 +345,9 @@ export function useObjects(appContext) {
|
|
|
345
345
|
placement,
|
|
346
346
|
);
|
|
347
347
|
const imported = publishImported(object, commandContext);
|
|
348
|
-
appContext.notify(
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
: `${object.name} added — type its real height in metres to set the scale`,
|
|
352
|
-
);
|
|
348
|
+
appContext.notify(ko(`${object.name} added — type its real height in metres to set the scale`,
|
|
349
|
+
`${object.name} 추가됨 — 실제 높이(m)를 입력하면 크기가 맞습니다`,
|
|
350
|
+
`${object.name} 已添加 — 输入实际高度(米)即可匹配尺寸`));
|
|
353
351
|
return imported;
|
|
354
352
|
} catch (error) {
|
|
355
353
|
throw new StudioProtocolError(error.code ?? "INVALID_ARGUMENT", `Could not import that image: ${error.message}`);
|
|
@@ -379,11 +377,9 @@ export function useObjects(appContext) {
|
|
|
379
377
|
placement,
|
|
380
378
|
);
|
|
381
379
|
const imported = publishImported(object, commandContext);
|
|
382
|
-
appContext.notify(
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
: `${object.name} added — type its real height in metres to set the scale`,
|
|
386
|
-
);
|
|
380
|
+
appContext.notify(ko(`${object.name} added — type its real height in metres to set the scale`,
|
|
381
|
+
`${object.name} 추가됨 — 실제 높이(m)를 입력하면 크기가 맞습니다`,
|
|
382
|
+
`${object.name} 已添加 — 输入实际高度(米)即可匹配尺寸`));
|
|
387
383
|
return imported;
|
|
388
384
|
}
|
|
389
385
|
|
|
@@ -479,11 +475,9 @@ export function useObjects(appContext) {
|
|
|
479
475
|
placementInFrontOfShot(),
|
|
480
476
|
);
|
|
481
477
|
const imported = publishImported(object, commandContext);
|
|
482
|
-
appContext.notify(
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
: `${object.name} added — type its real height in metres to set the scale`,
|
|
486
|
-
);
|
|
478
|
+
appContext.notify(ko(`${object.name} added — type its real height in metres to set the scale`,
|
|
479
|
+
`${object.name} 추가됨 — 실제 높이(m)를 입력하면 크기가 맞습니다`,
|
|
480
|
+
`${object.name} 已添加 — 输入实际高度(米)即可匹配尺寸`));
|
|
487
481
|
return imported;
|
|
488
482
|
} catch (error) {
|
|
489
483
|
throw new StudioProtocolError(error.code ?? "INVALID_ARGUMENT", `Could not import that model: ${error.message}`);
|
|
@@ -520,11 +514,9 @@ export function useObjects(appContext) {
|
|
|
520
514
|
placement,
|
|
521
515
|
);
|
|
522
516
|
const imported = publishImported(object, commandContext);
|
|
523
|
-
appContext.notify(
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
: `${object.name} added — type its real height in metres to set the scale`,
|
|
527
|
-
);
|
|
517
|
+
appContext.notify(ko(`${object.name} added — type its real height in metres to set the scale`,
|
|
518
|
+
`${object.name} 추가됨 — 실제 높이(m)를 입력하면 크기가 맞습니다`,
|
|
519
|
+
`${object.name} 已添加 — 输入实际高度(米)即可匹配尺寸`));
|
|
528
520
|
return imported;
|
|
529
521
|
}
|
|
530
522
|
|
|
@@ -569,11 +561,9 @@ export function useObjects(appContext) {
|
|
|
569
561
|
aspect: cut.asset.width / cut.asset.height,
|
|
570
562
|
height: fullFrameHeight * cut.heightScale,
|
|
571
563
|
})));
|
|
572
|
-
appContext.notify(
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
: `${object.name} — ${Math.round(cut.removed * 100)}% removed. The original and your selection are kept`,
|
|
576
|
-
);
|
|
564
|
+
appContext.notify(ko(`${object.name} — ${Math.round(cut.removed * 100)}% removed. The original and your selection are kept`,
|
|
565
|
+
`${object.name} 배경 제거 — ${Math.round(cut.removed * 100)}% 지움. 원본과 칠한 영역은 그대로 남습니다`,
|
|
566
|
+
`${object.name} 背景已移除 — 清除 ${Math.round(cut.removed * 100)}%。原图和选区仍保留`));
|
|
577
567
|
return { objectId: object.id };
|
|
578
568
|
} catch (error) {
|
|
579
569
|
throw new StudioProtocolError(error.code ?? "TARGET_NOT_READY", `Could not remove the background: ${error.message}`);
|
|
@@ -606,7 +596,7 @@ export function useObjects(appContext) {
|
|
|
606
596
|
const placed = { ...object, id: copy.id, name: copy.name, x: object.x + 0.5 };
|
|
607
597
|
domain.write((objects) => [...objects, placed]);
|
|
608
598
|
appContext.shared.setSelectedHierarchyId(`object:${placed.id}`);
|
|
609
|
-
appContext.notify((isKo, ko) =>
|
|
599
|
+
appContext.notify((isKo, ko) => ko(`${placed.name} duplicated`, `${sceneObjectNameDisplayKo(placed.name)} 복제됨`, `${sceneObjectNameDisplayKo(placed.name)}已复制`));
|
|
610
600
|
}
|
|
611
601
|
|
|
612
602
|
function frameSelection(id = selectedSceneObjectId) {
|
|
@@ -676,7 +666,7 @@ export function useObjects(appContext) {
|
|
|
676
666
|
? ko("Character 1", "인물 1")
|
|
677
667
|
: index === 1
|
|
678
668
|
? ko("Character 2", "인물 2")
|
|
679
|
-
:
|
|
669
|
+
: ko(`Character ${index + 1}`, `인물 ${index + 1}`, `角色 ${index + 1}`);
|
|
680
670
|
const bone = attach.bone
|
|
681
671
|
? HIERARCHY_INSPECTOR_TITLES[`rig.${attach.bone}`] ?? attach.bone
|
|
682
672
|
: ko("Root", "루트");
|
package/src/domains/scenes.js
CHANGED
|
@@ -30,9 +30,7 @@ import {
|
|
|
30
30
|
PROJECT_EXTENSION,
|
|
31
31
|
normalizeWorkflowGraph,
|
|
32
32
|
storeWorkflowGraph,
|
|
33
|
-
pickProjectFileForOpen,
|
|
34
33
|
readProjectFile,
|
|
35
|
-
openProjectFallback,
|
|
36
34
|
readProjectDocument,
|
|
37
35
|
requestHandlePermission,
|
|
38
36
|
createWorkflowGraph,
|
|
@@ -42,11 +40,12 @@ import {
|
|
|
42
40
|
buildAnimationProjectFromStoryboard,
|
|
43
41
|
} from "../project.js";
|
|
44
42
|
import { playgroundSceneUrl, fetchSceneProject } from "../playground.js";
|
|
43
|
+
import { pickProjectToOpen } from "../project-open-file.js";
|
|
45
44
|
import { openAssetDb, referencedAssetIds, getAsset, putAsset } from "../scene-assets.js";
|
|
46
45
|
import { internWorkflowOutputs, workflowOutputRefs, resolveWorkflowOutputs } from "../workflow/workflow-resources.js";
|
|
47
46
|
|
|
48
47
|
import { resourceManifest } from "../project-resources.js";
|
|
49
|
-
import {
|
|
48
|
+
import { ko } from "../locale.js";
|
|
50
49
|
import { track, bucketCount, bucketProjectAge, startExportAttempt } from "../analytics.js";
|
|
51
50
|
import { mergeProjectCustomPoses } from "../project-poses.js";
|
|
52
51
|
import { DEFAULT_WORKSPACE_LAYOUT, DEFAULT_DURATION_S, TIMELINE_FPS } from "../app-stage.jsx";
|
|
@@ -270,9 +269,9 @@ export function useScenes(appContext) {
|
|
|
270
269
|
function projectProblemsNotice(problems) {
|
|
271
270
|
if (!Array.isArray(problems) || !problems.length) return "";
|
|
272
271
|
const codes = [...new Set(problems.map((problem) => problem?.code).filter(Boolean))].join(", ");
|
|
273
|
-
return
|
|
274
|
-
|
|
275
|
-
|
|
272
|
+
return ko(` · skipped ${problems.length} embedded resource${problems.length === 1 ? "" : "s"}${codes ? ` (${codes})` : ""}`,
|
|
273
|
+
` · 포함된 자원 ${problems.length}개를 건너뛰었어요${codes ? ` (${codes})` : ""}`,
|
|
274
|
+
` · 已跳过 ${problems.length} 个嵌入资源${codes ? `(${codes})` : ""}`);
|
|
276
275
|
}
|
|
277
276
|
|
|
278
277
|
async function rehydrateProjectAssets(project, warnings = []) {
|
|
@@ -344,7 +343,7 @@ export function useScenes(appContext) {
|
|
|
344
343
|
object_count_bucket: bucketCount(appContext.shared.projectStateRef.current.sceneObjects?.length ?? 0),
|
|
345
344
|
shot_count_bucket: bucketCount(appContext.shared.shots.length),
|
|
346
345
|
});
|
|
347
|
-
appContext.notify((isKo, ko) =>
|
|
346
|
+
appContext.notify((isKo, ko) => ko(`Project saved: ${name}${PROJECT_EXTENSION}`, `프로젝트 저장됨: ${name}${PROJECT_EXTENSION}`, `项目已保存:${name}${PROJECT_EXTENSION}`));
|
|
348
347
|
return { saved: true, name, fileName: downloaded ? `${name}${PROJECT_EXTENSION}` : appContext.shared.projectHandleRef.current?.name ?? `${name}${PROJECT_EXTENSION}`, downloaded };
|
|
349
348
|
} catch (err) {
|
|
350
349
|
if (err?.name === "AbortError") {
|
|
@@ -444,18 +443,12 @@ export function useScenes(appContext) {
|
|
|
444
443
|
async function openProject(context = null) {
|
|
445
444
|
if (!context) return runProject("project.open");
|
|
446
445
|
try {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
if (hasFileSystemAccess()) {
|
|
450
|
-
handle = await pickProjectFileForOpen();
|
|
451
|
-
file = await readProjectFile(handle);
|
|
452
|
-
} else {
|
|
453
|
-
file = await openProjectFallback();
|
|
454
|
-
}
|
|
446
|
+
const { file, handle = null, blocked } = await pickProjectToOpen();
|
|
447
|
+
if (blocked) throw new StudioProtocolError("TARGET_NOT_READY", ko("This browser would not read that file. Open it once more.", "이 브라우저가 그 파일을 읽지 못했어요. 한 번 더 열어 주세요."));
|
|
455
448
|
if (!file) return false;
|
|
456
449
|
const result = readProjectDocument(file.text);
|
|
457
450
|
if (!result.ok) {
|
|
458
|
-
appContext.notify(
|
|
451
|
+
appContext.notify(ko(`Cannot open project: ${result.reason}`, `프로젝트를 열 수 없어요: ${result.reason}`, `无法打开项目:${result.reason}`));
|
|
459
452
|
return false;
|
|
460
453
|
}
|
|
461
454
|
result.project.savedAt = result.project.savedAt ?? file.savedAt ?? null;
|
|
@@ -465,7 +458,7 @@ export function useScenes(appContext) {
|
|
|
465
458
|
context.check();
|
|
466
459
|
applyProject(result.project, true);
|
|
467
460
|
setProjectStartupOpen(false);
|
|
468
|
-
appContext.notify(`${
|
|
461
|
+
appContext.notify(`${ko(`Project opened: ${result.project.name}`, `프로젝트 열림: ${result.project.name}`, `项目已打开:${result.project.name}`)}${projectProblemsNotice(result.problems)}`);
|
|
469
462
|
return true;
|
|
470
463
|
} catch (err) {
|
|
471
464
|
if (err?.name === "AbortError") return false;
|
|
@@ -490,7 +483,7 @@ export function useScenes(appContext) {
|
|
|
490
483
|
const file = await readProjectFile(handle);
|
|
491
484
|
const result = readProjectDocument(file.text);
|
|
492
485
|
if (!result.ok) {
|
|
493
|
-
appContext.notify(
|
|
486
|
+
appContext.notify(ko(`Cannot open project: ${result.reason}`, `프로젝트를 열 수 없어요: ${result.reason}`, `无法打开项目:${result.reason}`));
|
|
494
487
|
return false;
|
|
495
488
|
}
|
|
496
489
|
result.project.savedAt = result.project.savedAt ?? file.savedAt ?? null;
|
|
@@ -501,7 +494,7 @@ export function useScenes(appContext) {
|
|
|
501
494
|
applyProject(result.project, true);
|
|
502
495
|
setProjectBrowserOpen(false);
|
|
503
496
|
setProjectStartupOpen(false);
|
|
504
|
-
appContext.notify(`${
|
|
497
|
+
appContext.notify(`${ko(`Project opened: ${result.project.name}`, `프로젝트 열림: ${result.project.name}`, `项目已打开:${result.project.name}`)}${projectProblemsNotice(result.problems)}`);
|
|
505
498
|
return true;
|
|
506
499
|
} catch (err) {
|
|
507
500
|
if (err?.code) throw err;
|
|
@@ -543,7 +536,7 @@ export function useScenes(appContext) {
|
|
|
543
536
|
setProjectStartupOpen(false);
|
|
544
537
|
appContext.shared.setFirstSuccessGuideOpen(true);
|
|
545
538
|
domain.pendingCheckpoint = { clock: appContext.undoClock, name };
|
|
546
|
-
appContext.notify(ko(`New project: ${name}`, `새 프로젝트: ${name}`));
|
|
539
|
+
appContext.notify(ko(`New project: ${name}`, `새 프로젝트: ${name}`, `新项目:${name}`));
|
|
547
540
|
return true;
|
|
548
541
|
}
|
|
549
542
|
|
|
@@ -560,7 +553,7 @@ export function useScenes(appContext) {
|
|
|
560
553
|
await rehydrateProjectAssets(result.project, result.warnings);
|
|
561
554
|
context.check();
|
|
562
555
|
applyProject(result.project, true);
|
|
563
|
-
appContext.notify(`${
|
|
556
|
+
appContext.notify(`${ko(`Project restored: ${result.project.name}`, `프로젝트 복원됨: ${result.project.name}`, `项目已恢复:${result.project.name}`)}${projectProblemsNotice(result.problems)}`);
|
|
564
557
|
return true;
|
|
565
558
|
} catch (error) {
|
|
566
559
|
if (error?.code) throw error;
|
package/src/domains/shots.js
CHANGED
|
@@ -16,7 +16,7 @@ import { followFramingFromCamera, craneHeightAt, buildRail, simplifyStroke } fro
|
|
|
16
16
|
import { defaultRailRange, clampRailRange } from '../camera-rail-schedule.js';
|
|
17
17
|
import { timelineSpan, timelineContentExtent } from '../timeline-extent.js';
|
|
18
18
|
import { trackFeature } from '../analytics.js';
|
|
19
|
-
import { ko
|
|
19
|
+
import { ko } from '../locale.js';
|
|
20
20
|
|
|
21
21
|
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
22
22
|
function normalizeState(value) {
|
|
@@ -367,9 +367,9 @@ export function useShots(appContext) {
|
|
|
367
367
|
// In the landing playground a drawn rail opens the player
|
|
368
368
|
// on the shot camera, but leave playback to the visitor.
|
|
369
369
|
appContext.shared.enterPreview(); appContext.shared.setTlPlaying(false); setTlFrame(activeShot.startFrame);
|
|
370
|
-
appContext.notify(
|
|
370
|
+
appContext.notify(ko('Rail drawn — you are looking through the shot camera. Press ▶ to ride it; Esc goes back to flying.', '레일 완성 — 샷 카메라 시점으로 전환했습니다. ▶ 로 재생, Esc 로 복귀', '轨道已完成 — 已切换到镜头摄影机视角。按 ▶ 播放,按 Esc 返回自由视角。')); return;
|
|
371
371
|
}
|
|
372
|
-
appContext.notify(
|
|
372
|
+
appContext.notify(ko(`Camera rail drawn — ${curve ? curve.length.toFixed(1) : '?'} m, ${simplified.length} control points`, `카메라 레일 완성 — ${curve ? curve.length.toFixed(1) : '?'} m, 제어점 ${simplified.length}개`, `摄影机轨道已完成 — ${curve ? curve.length.toFixed(1) : '?'} m,${simplified.length} 个控制点`));
|
|
373
373
|
}
|
|
374
374
|
function publishStudioShots(value) { return appContext.storeDomain('shot').publish(value); }
|
|
375
375
|
function commitStudioShots(draft) { return appContext.storeDomain('shot').commitDraft(draft); }
|
|
@@ -5,26 +5,34 @@ const ANIMATION_STEPS = [
|
|
|
5
5
|
{
|
|
6
6
|
en: "Select a character in the Outliner.",
|
|
7
7
|
ko: "아웃라이너에서 캐릭터를 선택하세요.",
|
|
8
|
+
zh: "在大纲视图中选择一个角色。",
|
|
8
9
|
hintEn: "Click Character 1 (or its row) to make it active.",
|
|
9
10
|
hintKo: "캐릭터 1(또는 캐릭터 행)을 클릭하면 활성화됩니다.",
|
|
11
|
+
hintZh: "点击角色 1(或其所在行)使其成为当前角色。",
|
|
10
12
|
},
|
|
11
13
|
{
|
|
12
14
|
en: "Move it in the Scene view.",
|
|
13
15
|
ko: "장면 보기에서 캐릭터를 움직이세요.",
|
|
16
|
+
zh: "在场景视图中移动它。",
|
|
14
17
|
hintEn: "Drag the character marker on the floor to place it.",
|
|
15
18
|
hintKo: "바닥의 캐릭터 마커를 드래그해 배치하세요.",
|
|
19
|
+
hintZh: "拖动地面上的角色标记来摆放位置。",
|
|
16
20
|
},
|
|
17
21
|
{
|
|
18
22
|
en: "Ask the Agent for a motion.",
|
|
19
23
|
ko: "에이전트에게 동작을 요청하세요.",
|
|
24
|
+
zh: "让智能体生成一个动作。",
|
|
20
25
|
hintEn: "Open it with Cmd/Ctrl+B and say what the character does; the take lands on the Body lane.",
|
|
21
26
|
hintKo: "Cmd/Ctrl+B로 열고 캐릭터가 할 동작을 말하면 테이크가 바디 레인에 올라갑니다.",
|
|
27
|
+
hintZh: "按 Cmd/Ctrl+B 打开,说出角色要做的动作;动作片段会落在身体轨道上。",
|
|
22
28
|
},
|
|
23
29
|
{
|
|
24
30
|
en: "Scrub the timeline or press Space to play.",
|
|
25
31
|
ko: "타임라인을 문지르거나 Space를 눌러 재생하세요.",
|
|
32
|
+
zh: "拖动时间线或按空格播放。",
|
|
26
33
|
hintEn: "Seeing the playhead move is your first visible result.",
|
|
27
34
|
hintKo: "재생 헤드가 움직이면 첫 결과를 확인한 것입니다.",
|
|
35
|
+
hintZh: "看到播放头移动,就是你的第一个可见结果。",
|
|
28
36
|
},
|
|
29
37
|
];
|
|
30
38
|
|
|
@@ -32,26 +40,34 @@ const STORYBOARD_STEPS = [
|
|
|
32
40
|
{
|
|
33
41
|
en: "Open the Agent panel (Cmd/Ctrl+B).",
|
|
34
42
|
ko: "에이전트 패널을 여세요 (Cmd/Ctrl+B).",
|
|
43
|
+
zh: "打开智能体面板(Cmd/Ctrl+B)。",
|
|
35
44
|
hintEn: "The Agent panel is where you describe each panel.",
|
|
36
45
|
hintKo: "에이전트 패널에서 각 패널을 설명합니다.",
|
|
46
|
+
hintZh: "在智能体面板中逐个描述每个面板。",
|
|
37
47
|
},
|
|
38
48
|
{
|
|
39
49
|
en: "Describe one scene in a sentence.",
|
|
40
50
|
ko: "장면 하나를 한 문장으로 설명하세요.",
|
|
51
|
+
zh: "用一句话描述一个场景。",
|
|
41
52
|
hintEn: "One sentence per turn is enough for one panel.",
|
|
42
53
|
hintKo: "패널 하나에는 한 문장이면 충분합니다.",
|
|
54
|
+
hintZh: "每个面板一轮只说一句话就够了。",
|
|
43
55
|
},
|
|
44
56
|
{
|
|
45
57
|
en: "Your panel appears on the Board.",
|
|
46
58
|
ko: "패널이 보드에 나타납니다.",
|
|
59
|
+
zh: "你的面板会出现在看板上。",
|
|
47
60
|
hintEn: "Open the Board tab to see the new panel card.",
|
|
48
61
|
hintKo: "보드 탭에서 새 패널 카드를 확인하세요.",
|
|
62
|
+
hintZh: "打开看板标签页查看新的面板卡片。",
|
|
49
63
|
},
|
|
50
64
|
{
|
|
51
65
|
en: "Stylize it, or export the panel pack.",
|
|
52
66
|
ko: "스타일을 입히거나 패널 팩으로 내보내세요.",
|
|
67
|
+
zh: "对它进行风格化,或导出面板包。",
|
|
53
68
|
hintEn: "Use Stylize on the card, or export the panel pack.",
|
|
54
69
|
hintKo: "카드에서 스타일을 입히거나 패널 팩으로 내보내세요.",
|
|
70
|
+
hintZh: "在卡片上使用风格化,或导出面板包。",
|
|
55
71
|
},
|
|
56
72
|
];
|
|
57
73
|
|
|
@@ -73,7 +89,7 @@ export default function FirstSuccessGuide({ open, onDismiss, previsMode = "anima
|
|
|
73
89
|
<div className="v2-first-success-guide-head first-success-guide-head">
|
|
74
90
|
<div>
|
|
75
91
|
<strong id="first-success-guide-title">{ko("Your first 60 seconds", "첫 60초")}</strong>
|
|
76
|
-
<span>{complete ? ko("Core loop complete", "핵심 흐름 완료") : ko(`Step ${step + 1} of ${STEPS.length}`, `${step + 1} / ${STEPS.length}
|
|
92
|
+
<span>{complete ? ko("Core loop complete", "핵심 흐름 완료", "核心流程完成") : ko(`Step ${step + 1} of ${STEPS.length}`, `${step + 1} / ${STEPS.length} 단계`, `第 ${step + 1} / ${STEPS.length} 步`)}</span>
|
|
77
93
|
</div>
|
|
78
94
|
<button type="button" className="v2-first-success-guide-close first-success-guide-close" onClick={onDismiss} aria-label={ko("Dismiss guide", "가이드 닫기")}>×</button>
|
|
79
95
|
</div>
|
|
@@ -91,12 +107,12 @@ export default function FirstSuccessGuide({ open, onDismiss, previsMode = "anima
|
|
|
91
107
|
{STEPS.map((item, index) => (
|
|
92
108
|
<li key={item.en} className={index < step ? "done" : index === step ? "current" : ""}>
|
|
93
109
|
<span className="v2-first-success-guide-step-mark first-success-guide-step-mark">{index < step ? "✓" : index + 1}</span>
|
|
94
|
-
<div><strong>{ko(item.en, item.ko)}</strong>{index === step && <span>{ko(item.hintEn, item.hintKo)}</span>}</div>
|
|
110
|
+
<div><strong>{ko(item.en, item.ko, item.zh)}</strong>{index === step && <span>{ko(item.hintEn, item.hintKo, item.hintZh)}</span>}</div>
|
|
95
111
|
</li>
|
|
96
112
|
))}
|
|
97
113
|
</ol>
|
|
98
114
|
<button type="button" className="v2-first-success-guide-next first-success-guide-next btn primary" onClick={() => setStep((value) => value + 1)}>
|
|
99
|
-
{ko(step === STEPS.length - 1 ? "Mark complete" : "I did this", step === STEPS.length - 1 ? "완료로 표시" : "했어요")}
|
|
115
|
+
{ko(step === STEPS.length - 1 ? "Mark complete" : "I did this", step === STEPS.length - 1 ? "완료로 표시" : "했어요", step === STEPS.length - 1 ? "标记完成" : "我做了这一步")}
|
|
100
116
|
</button>
|
|
101
117
|
</>
|
|
102
118
|
)}
|
package/src/hierarchy-panel.jsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { createPortal } from "react-dom";
|
|
3
|
-
import { ko
|
|
3
|
+
import { ko } from "./locale.js";
|
|
4
4
|
import { buildHierarchyNodes } from "./hierarchy-model.js";
|
|
5
5
|
import { sceneObjectIdFromHierarchy } from "./scene-objects.js";
|
|
6
6
|
import { CatalogueEntries, displayObjectLabel } from "./object-catalog.jsx";
|
|
@@ -62,9 +62,8 @@ export const HIERARCHY_DRAG_MIME = "application/x-cclay-hierarchy";
|
|
|
62
62
|
const carriesHierarchyRow = (event) => !!event.dataTransfer?.types?.includes?.(HIERARCHY_DRAG_MIME);
|
|
63
63
|
|
|
64
64
|
function displaySceneName(name) {
|
|
65
|
-
if (!isKo) return name;
|
|
66
65
|
const generatedName = /^SCENE\s+(\d+)$/i.exec(name);
|
|
67
|
-
return generatedName ? ko(
|
|
66
|
+
return generatedName ? ko(name, `장면 ${generatedName[1]}`, `场景 ${generatedName[1]}`) : name;
|
|
68
67
|
}
|
|
69
68
|
|
|
70
69
|
/**
|
|
@@ -112,7 +111,7 @@ function ScenePill({ scenes, activeSceneId, onSceneSelect, onSceneCreate, onRena
|
|
|
112
111
|
function displayHierarchyLabel(node) {
|
|
113
112
|
if (node.kind === "object") return displayObjectLabel(node.label);
|
|
114
113
|
if (node.kind === "scene") return displaySceneName(node.label);
|
|
115
|
-
return
|
|
114
|
+
return HIERARCHY_LABELS_KO[node.label] ? ko(node.label, HIERARCHY_LABELS_KO[node.label]) : node.label;
|
|
116
115
|
}
|
|
117
116
|
|
|
118
117
|
const OUTLINER_TYPES = {
|
|
@@ -466,7 +465,7 @@ function TreeRow({
|
|
|
466
465
|
<button
|
|
467
466
|
type="button"
|
|
468
467
|
className="hierarchy-toggle"
|
|
469
|
-
aria-label={
|
|
468
|
+
aria-label={ko(`${expanded ? "Collapse" : "Expand"} ${label}`, `${label} ${expanded ? "접기" : "펼치기"}`, `${expanded ? "折叠" : "展开"} ${label}`)}
|
|
470
469
|
onClick={() => onToggle(node.id)}
|
|
471
470
|
>
|
|
472
471
|
{expanded ? "▾" : "▸"}
|
|
@@ -486,7 +485,7 @@ function TreeRow({
|
|
|
486
485
|
ref={inputRef}
|
|
487
486
|
className="hierarchy-rename-input"
|
|
488
487
|
defaultValue={node.label}
|
|
489
|
-
aria-label={
|
|
488
|
+
aria-label={ko(`Rename ${label}`, `${label} 이름 바꾸기`, `重命名 ${label}`)}
|
|
490
489
|
autoFocus
|
|
491
490
|
onFocus={(event) => event.currentTarget.select()}
|
|
492
491
|
onKeyDown={(event) => {
|
|
@@ -523,7 +522,7 @@ function TreeRow({
|
|
|
523
522
|
className="hierarchy-eye"
|
|
524
523
|
data-hidden={node.hidden ? "true" : undefined}
|
|
525
524
|
aria-pressed={node.hidden === true}
|
|
526
|
-
aria-label={node.hidden ? (
|
|
525
|
+
aria-label={node.hidden ? ko(`Show ${label}`, `${label} 표시`, `显示 ${label}`) : ko(`Hide ${label}`, `${label} 숨기기`, `隐藏 ${label}`)}
|
|
527
526
|
onClick={(event) => {
|
|
528
527
|
event.preventDefault();
|
|
529
528
|
event.stopPropagation();
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Chinese UI copy for App-level toasts, export status, error boundary and first-run guide.
|
|
2
|
+
export const ZH_APP = {
|
|
3
|
+
"Load a character to pose it — Pose mode edits its rig with IK": "加载角色后才能摆姿势 — 姿势模式用 IK 编辑其骨骼",
|
|
4
|
+
"That picture format is not supported — use PNG, JPG, WebP or GIF": "不支持该图片格式 — 请使用 PNG、JPG、WebP 或 GIF",
|
|
5
|
+
"A newer edit comes after this deletion. Use Undo history instead.": "此删除之后还有更新的编辑。请改用撤销历史。",
|
|
6
|
+
"Double-click the line to add a dot": "双击线条添加一个点",
|
|
7
|
+
"That image is no longer in storage": "该图片已不在存储中",
|
|
8
|
+
"This image's usage changed, so it was not deleted. Please review it again.": "该图片的使用情况已变化,因此未删除。请重新检查。",
|
|
9
|
+
"This image's scene references changed, so it was not deleted. Please review it again.": "该图片的场景引用已变化,因此未删除。请重新检查。",
|
|
10
|
+
"That image is used by a scene and was not deleted": "该图片正被场景使用,未删除",
|
|
11
|
+
"That image is now used by a scene and was not deleted": "该图片现在正被场景使用,未删除",
|
|
12
|
+
"The scene changed while deleting, so the image was kept. Please review storage again.": "删除期间场景发生变化,图片已保留。请重新检查存储。",
|
|
13
|
+
"The live editor is disconnected. Reconnect before sending.": "实时编辑器已断开。请重新连接后再发送。",
|
|
14
|
+
"MP4 encoding is unavailable. Use a current browser with H.264 WebCodecs support (such as Chrome or Edge), enable hardware acceleration, then retry.": "MP4 编码不可用。请使用支持 H.264 WebCodecs 的最新浏览器(如 Chrome 或 Edge),开启硬件加速后重试。",
|
|
15
|
+
"Video encoding failed. Retry the same request. If resources are tight, shorten the shot range or lower output resolution before starting a new export.": "视频编码失败。请重试相同请求。若资源紧张,请缩短镜头范围或降低输出分辨率后重新导出。",
|
|
16
|
+
"Frame rendering failed. Let the scene finish loading, then retry. If it repeats, shorten the range or reduce output resolution for a new export.": "帧渲染失败。请等场景加载完成后再试。若反复出现,请缩短范围或降低输出分辨率后重新导出。",
|
|
17
|
+
"Export cancelled. No further files will be handed off.": "导出已取消。不会再交付其他文件。",
|
|
18
|
+
"Export could not finish. Retry the same request. For memory or resource problems, close other heavy tabs or use a shorter range / lower output resolution in a new export.": "导出未能完成。请重试相同请求。若为内存或资源问题,请关闭其他占用较大的标签页,或在新的导出中使用更短的范围/更低的分辨率。",
|
|
19
|
+
"An export is already running": "已有导出正在进行",
|
|
20
|
+
"Export completed. Check your export folder or browser downloads; the OS save is not confirmed.": "导出已完成。请查看导出文件夹或浏览器下载;系统保存未确认。",
|
|
21
|
+
"Export pipeline completed. The requesting tool handles the file handoff.": "导出流程已完成。文件交付由发起请求的工具处理。",
|
|
22
|
+
"Cancelling at the current work boundary…": "正在当前工作边界处取消…",
|
|
23
|
+
"Preparing": "准备中",
|
|
24
|
+
"Encoding": "编码中",
|
|
25
|
+
"Finalizing": "收尾中",
|
|
26
|
+
"Completed": "已完成",
|
|
27
|
+
"Failed": "失败",
|
|
28
|
+
"Cancelled": "已取消",
|
|
29
|
+
"Depth range sampled": "已采样深度范围",
|
|
30
|
+
"Frames submitted to encoder": "已提交给编码器的帧",
|
|
31
|
+
"Finalizing MP4. Progress is indeterminate; this stage cannot be interrupted.": "正在完成 MP4。进度不确定;此阶段无法中断。",
|
|
32
|
+
"Finishing encoder output. Progress is indeterminate.": "正在完成编码器输出。进度不确定。",
|
|
33
|
+
"Handing off existing PNGs. The file handoff cannot be cancelled.": "正在交付现有 PNG。文件交付无法取消。",
|
|
34
|
+
"Building the ZIP. Cancel takes effect after the current work unit, before the file handoff.": "正在构建 ZIP。取消将在当前工作单元结束后、文件交付前生效。",
|
|
35
|
+
"Cancel stops at the next frame boundary, before the file handoff.": "取消将在下一个帧边界停止,届时尚未交付文件。",
|
|
36
|
+
"Preparing the renderer and checking MP4 support…": "正在准备渲染器并检查 MP4 支持…",
|
|
37
|
+
"Encoding the requested frames, not yet a completed file.": "正在编码所请求的帧,尚未生成完整文件。",
|
|
38
|
+
"Retry keeps the original shot, camera, range and settings; it does not change your edits.": "重试会沿用原镜头、摄影机、范围与设置,不会改变你的编辑。",
|
|
39
|
+
"Cancel export": "取消导出",
|
|
40
|
+
"Retry same export": "重试相同导出",
|
|
41
|
+
"Add at least one Shot before exporting OTIO": "导出 OTIO 前请至少添加一个镜头",
|
|
42
|
+
"The shot renderer is not ready": "镜头渲染器尚未就绪",
|
|
43
|
+
"Add at least one Shot before exporting a keyframe pack": "导出关键帧包前请至少添加一个镜头",
|
|
44
|
+
"Depth and normal passes exported": "深度和法线通道已导出",
|
|
45
|
+
"Add at least one Shot before exporting a storyboard": "导出故事板前请至少添加一个镜头",
|
|
46
|
+
"Composing the storyboard…": "正在编排故事板…",
|
|
47
|
+
"Add a panel first — storyboard exports describe panels": "请先添加面板 — 故事板导出需要面板",
|
|
48
|
+
"ROOT PATH": "根路径",
|
|
49
|
+
"Prompt copied to clipboard": "提示词已复制到剪贴板",
|
|
50
|
+
"Frame export finished. Check your export folder or browser downloads.": "帧导出完成。请查看导出文件夹或浏览器下载。",
|
|
51
|
+
"Redone": "已重做",
|
|
52
|
+
"Undone": "已撤销",
|
|
53
|
+
"Saving…": "保存中…",
|
|
54
|
+
"Save failed": "保存失败",
|
|
55
|
+
"Not saved": "未保存",
|
|
56
|
+
"Unsaved changes": "未保存的更改",
|
|
57
|
+
"Saved": "已保存",
|
|
58
|
+
"Sign in with ChatGPT in the Agent panel to generate a blueprint.": "请在智能体面板中登录 ChatGPT 以生成蓝图。",
|
|
59
|
+
"Sign in to the Agent (⌘B) to sculpt from an image.": "请登录智能体(⌘B)以从图像雕塑。",
|
|
60
|
+
"The live editor is disconnected — reconnect to sculpt.": "实时编辑器已断开 — 请重新连接后再雕塑。",
|
|
61
|
+
"No model is available — add a provider key in the Agent panel.": "没有可用模型 — 请在智能体面板中添加服务商密钥。",
|
|
62
|
+
"New sculpt": "新建雕塑",
|
|
63
|
+
"Add a reference image in the Sculpt panel and press Start — or select a sculpt and press 5.": "在雕塑面板中添加参考图并按开始 — 或选中雕塑并按 5。",
|
|
64
|
+
"The graphics context was lost — restoring the stage. If it stays black, reload the page; your work is autosaved.": "图形上下文已丢失 — 正在恢复舞台。若持续黑屏,请刷新页面;你的工作已自动保存。",
|
|
65
|
+
"Graphics restored.": "图形已恢复。",
|
|
66
|
+
"Dot added — drag it to bend the route": "已添加点 — 拖动它使路径弯曲",
|
|
67
|
+
"3D graphics (WebGL) is turned off in this browser": "此浏览器的 3D 图形(WebGL)已关闭",
|
|
68
|
+
"Quit the browser completely (Cmd+Q) and reopen it, or turn on \"Use graphics acceleration when available\" in its settings. chrome://gpu shows the WebGL status.": "请完全退出浏览器(Cmd+Q)后重新打开,或在设置中开启“尽可能使用图形加速”。chrome://gpu 可查看 WebGL 状态。",
|
|
69
|
+
"The 3D view lost its graphics context": "3D 视图丢失了图形上下文",
|
|
70
|
+
"Waiting for the browser to restore it. If this stays, reload the studio — scenes autosave.": "正在等待浏览器恢复。若持续如此,请刷新工作室 — 场景会自动保存。",
|
|
71
|
+
"Reload": "刷新",
|
|
72
|
+
"Click or ▸ to expand · drag to move": "点击或 ▸ 展开 · 拖动移动",
|
|
73
|
+
"Click or ▾ to fold · drag to move": "点击或 ▾ 折叠 · 拖动移动",
|
|
74
|
+
"Expand inset view": "展开小窗视图",
|
|
75
|
+
"Collapse inset view": "折叠小窗视图",
|
|
76
|
+
"Shot view": "镜头视图",
|
|
77
|
+
"Top-View": "顶视图",
|
|
78
|
+
"Resize inset view": "调整小窗大小",
|
|
79
|
+
"Shot": "镜头",
|
|
80
|
+
"Look through the shot camera": "通过镜头摄影机观察",
|
|
81
|
+
"Look through the shot camera — right-drag, WASD and orbit set the recording lens (Esc returns)": "通过镜头摄影机观察 — 右键拖动、WASD 和环绕设置录制镜头(Esc 返回)",
|
|
82
|
+
"Look through": "镜头视角",
|
|
83
|
+
"Return to the editor view (Esc)": "返回编辑器视图 (Esc)",
|
|
84
|
+
"Shot camera": "镜头摄影机",
|
|
85
|
+
"Esc · exit": "Esc · 退出",
|
|
86
|
+
"SUBJECT OUT OF FRAME": "角色不在画面内",
|
|
87
|
+
"A new version of CozyClay is ready.": "CozyClay 新版本已就绪。",
|
|
88
|
+
"Reload to update": "刷新以更新",
|
|
89
|
+
"Later": "稍后",
|
|
90
|
+
"Object deleted.": "物体已删除。",
|
|
91
|
+
"Undo object deletion": "撤销物体删除",
|
|
92
|
+
"Undo": "撤销",
|
|
93
|
+
"Project access was not granted.": "未获得项目访问权限。",
|
|
94
|
+
"Restore": "恢复",
|
|
95
|
+
"Dismiss": "关闭",
|
|
96
|
+
"Image deleted. This session can undo it.": "图片已删除。本次会话内可撤销。",
|
|
97
|
+
"This account cannot generate images.": "此账号无法生成图像。",
|
|
98
|
+
"Sign in with ChatGPT in the Agent panel first.": "请先在智能体面板中登录 ChatGPT。",
|
|
99
|
+
"unknown error": "未知错误",
|
|
100
|
+
"The image route did not return a PNG.": "图像接口未返回 PNG。",
|
|
101
|
+
"Draft": "草稿",
|
|
102
|
+
"The studio hit a render error": "工作室遇到渲染错误",
|
|
103
|
+
"Scenes autosave as you work, so reloading resumes from the last saved state.": "场景会随工作自动保存,刷新后会从上次保存的状态继续。",
|
|
104
|
+
"Reload the studio": "刷新工作室",
|
|
105
|
+
"Your first 60 seconds": "你的最初 60 秒",
|
|
106
|
+
"Core loop complete": "核心流程完成",
|
|
107
|
+
"Dismiss guide": "关闭指南",
|
|
108
|
+
"You made your first panel.": "你做出了第一个面板。",
|
|
109
|
+
"You made your first shot.": "你做出了第一个镜头。",
|
|
110
|
+
"Describe, review on the Board, then stylize or export. You can close this guide and keep adding panels.": "描述、在看板上检查,然后风格化或导出。你可以关闭本指南并继续添加面板。",
|
|
111
|
+
"Select, move, ask, and play are the core CozyClay loop. You can close this guide and keep blocking.": "选择、移动、提问和播放是 CozyClay 的核心流程。你可以关闭本指南并继续摆位。",
|
|
112
|
+
"Continue editing": "继续编辑",
|
|
113
|
+
"Follow these four actions to see a result quickly.": "跟随这四个步骤,快速看到结果。",
|
|
114
|
+
"Skip guide": "跳过指南",
|
|
115
|
+
"QA clean": "QA 通过",
|
|
116
|
+
"Settings": "设置",
|
|
117
|
+
"Language and analytics": "语言与统计",
|
|
118
|
+
"Medium": "中景",
|
|
119
|
+
"Wide": "广角",
|
|
120
|
+
"Close-Up": "特写",
|
|
121
|
+
"Low Angle": "低角度",
|
|
122
|
+
"High Angle": "高角度",
|
|
123
|
+
"Saved scenes were written by a newer CozyClay — they have been left untouched and this session will not save": "已保存的场景由更新版本的 CozyClay 创建 — 已保持原样,本次会话也不会保存",
|
|
124
|
+
};
|