다국어 자동생성
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
Layout,
|
||||
Monitor,
|
||||
Square,
|
||||
Languages,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
@@ -34,6 +35,8 @@ interface DesignerToolbarProps {
|
||||
isSaving?: boolean;
|
||||
showZoneBorders?: boolean;
|
||||
onToggleZoneBorders?: () => void;
|
||||
onGenerateMultilang?: () => void;
|
||||
isGeneratingMultilang?: boolean;
|
||||
}
|
||||
|
||||
export const DesignerToolbar: React.FC<DesignerToolbarProps> = ({
|
||||
@@ -50,6 +53,8 @@ export const DesignerToolbar: React.FC<DesignerToolbarProps> = ({
|
||||
isSaving = false,
|
||||
showZoneBorders = true,
|
||||
onToggleZoneBorders,
|
||||
onGenerateMultilang,
|
||||
isGeneratingMultilang = false,
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex items-center justify-between border-b border-gray-200 bg-gradient-to-r from-gray-50 to-white px-4 py-3 shadow-sm">
|
||||
@@ -226,6 +231,20 @@ export const DesignerToolbar: React.FC<DesignerToolbarProps> = ({
|
||||
|
||||
<div className="h-6 w-px bg-gray-300" />
|
||||
|
||||
{onGenerateMultilang && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={onGenerateMultilang}
|
||||
disabled={isGeneratingMultilang}
|
||||
className="flex items-center space-x-1"
|
||||
title="화면 라벨에 대한 다국어 키를 자동으로 생성합니다"
|
||||
>
|
||||
<Languages className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">{isGeneratingMultilang ? "생성 중..." : "다국어"}</span>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button onClick={onSave} disabled={isSaving} className="flex items-center space-x-2">
|
||||
<Save className="h-4 w-4" />
|
||||
<span>{isSaving ? "저장 중..." : "저장"}</span>
|
||||
|
||||
@@ -144,6 +144,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||
},
|
||||
});
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isGeneratingMultilang, setIsGeneratingMultilang] = useState(false);
|
||||
|
||||
// 🆕 화면에 할당된 메뉴 OBJID
|
||||
const [menuObjid, setMenuObjid] = useState<number | undefined>(undefined);
|
||||
@@ -1447,6 +1448,103 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||
}
|
||||
}, [selectedScreen, layout, screenResolution]);
|
||||
|
||||
// 다국어 자동 생성 핸들러
|
||||
const handleGenerateMultilang = useCallback(async () => {
|
||||
if (!selectedScreen?.screenId) {
|
||||
toast.error("화면 정보가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsGeneratingMultilang(true);
|
||||
|
||||
try {
|
||||
// 모든 컴포넌트에서 라벨 정보 추출
|
||||
const labels: Array<{ componentId: string; label: string; type?: string }> = [];
|
||||
|
||||
const extractLabels = (components: ComponentData[]) => {
|
||||
components.forEach((comp) => {
|
||||
const anyComp = comp as any;
|
||||
|
||||
// 라벨 추출
|
||||
if (anyComp.label && typeof anyComp.label === "string" && anyComp.label.trim()) {
|
||||
labels.push({
|
||||
componentId: comp.id,
|
||||
label: anyComp.label.trim(),
|
||||
type: "label",
|
||||
});
|
||||
}
|
||||
|
||||
// 제목 추출 (컨테이너, 카드 등)
|
||||
if (anyComp.title && typeof anyComp.title === "string" && anyComp.title.trim()) {
|
||||
labels.push({
|
||||
componentId: comp.id,
|
||||
label: anyComp.title.trim(),
|
||||
type: "title",
|
||||
});
|
||||
}
|
||||
|
||||
// 버튼 텍스트 추출
|
||||
if (anyComp.componentConfig?.text && typeof anyComp.componentConfig.text === "string") {
|
||||
labels.push({
|
||||
componentId: comp.id,
|
||||
label: anyComp.componentConfig.text.trim(),
|
||||
type: "button",
|
||||
});
|
||||
}
|
||||
|
||||
// placeholder 추출
|
||||
if (anyComp.placeholder && typeof anyComp.placeholder === "string" && anyComp.placeholder.trim()) {
|
||||
labels.push({
|
||||
componentId: comp.id,
|
||||
label: anyComp.placeholder.trim(),
|
||||
type: "placeholder",
|
||||
});
|
||||
}
|
||||
|
||||
// 자식 컴포넌트 재귀 탐색
|
||||
if (anyComp.children && Array.isArray(anyComp.children)) {
|
||||
extractLabels(anyComp.children);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
extractLabels(layout.components);
|
||||
|
||||
if (labels.length === 0) {
|
||||
toast.info("다국어로 변환할 라벨이 없습니다.");
|
||||
setIsGeneratingMultilang(false);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("🌐 다국어 자동 생성 요청:", {
|
||||
screenId: selectedScreen.screenId,
|
||||
menuObjid,
|
||||
labelsCount: labels.length,
|
||||
labels: labels.slice(0, 5), // 처음 5개만 로그
|
||||
});
|
||||
|
||||
// API 호출
|
||||
const { generateScreenLabelKeys } = await import("@/lib/api/multilang");
|
||||
const response = await generateScreenLabelKeys({
|
||||
screenId: selectedScreen.screenId,
|
||||
menuObjId: menuObjid?.toString(),
|
||||
labels,
|
||||
});
|
||||
|
||||
if (response.success && response.data) {
|
||||
toast.success(`${response.data.length}개의 다국어 키가 생성되었습니다.`);
|
||||
console.log("✅ 다국어 키 생성 완료:", response.data);
|
||||
} else {
|
||||
toast.error(response.error?.details || "다국어 키 생성에 실패했습니다.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ 다국어 생성 실패:", error);
|
||||
toast.error("다국어 키 생성 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsGeneratingMultilang(false);
|
||||
}
|
||||
}, [selectedScreen, layout.components, menuObjid]);
|
||||
|
||||
// 템플릿 드래그 처리
|
||||
const handleTemplateDrop = useCallback(
|
||||
(e: React.DragEvent, template: TemplateComponent) => {
|
||||
@@ -4217,6 +4315,8 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||
onBack={onBackToList}
|
||||
onSave={handleSave}
|
||||
isSaving={isSaving}
|
||||
onGenerateMultilang={handleGenerateMultilang}
|
||||
isGeneratingMultilang={isGeneratingMultilang}
|
||||
/>
|
||||
{/* 메인 컨테이너 (좌측 툴바 + 패널들 + 캔버스) */}
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import React from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Database, ArrowLeft, Save, Monitor, Smartphone } from "lucide-react";
|
||||
import { Database, ArrowLeft, Save, Monitor, Smartphone, Languages } from "lucide-react";
|
||||
import { ScreenResolution } from "@/types/screen";
|
||||
|
||||
interface SlimToolbarProps {
|
||||
@@ -13,6 +13,8 @@ interface SlimToolbarProps {
|
||||
onSave: () => void;
|
||||
isSaving?: boolean;
|
||||
onPreview?: () => void;
|
||||
onGenerateMultilang?: () => void;
|
||||
isGeneratingMultilang?: boolean;
|
||||
}
|
||||
|
||||
export const SlimToolbar: React.FC<SlimToolbarProps> = ({
|
||||
@@ -23,6 +25,8 @@ export const SlimToolbar: React.FC<SlimToolbarProps> = ({
|
||||
onSave,
|
||||
isSaving = false,
|
||||
onPreview,
|
||||
onGenerateMultilang,
|
||||
isGeneratingMultilang = false,
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex h-14 items-center justify-between border-b border-gray-200 bg-gradient-to-r from-gray-50 to-white px-4 shadow-sm">
|
||||
@@ -70,6 +74,18 @@ export const SlimToolbar: React.FC<SlimToolbarProps> = ({
|
||||
<span>반응형 미리보기</span>
|
||||
</Button>
|
||||
)}
|
||||
{onGenerateMultilang && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onGenerateMultilang}
|
||||
disabled={isGeneratingMultilang}
|
||||
className="flex items-center space-x-2"
|
||||
title="화면 라벨에 대한 다국어 키를 자동으로 생성합니다"
|
||||
>
|
||||
<Languages className="h-4 w-4" />
|
||||
<span>{isGeneratingMultilang ? "생성 중..." : "다국어 생성"}</span>
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={onSave} disabled={isSaving} className="flex items-center space-x-2">
|
||||
<Save className="h-4 w-4" />
|
||||
<span>{isSaving ? "저장 중..." : "저장"}</span>
|
||||
|
||||
Reference in New Issue
Block a user