다국어 자동생성

This commit is contained in:
kjs
2026-01-14 10:20:27 +09:00
parent 18b5161398
commit 61a7f585b4
8 changed files with 591 additions and 1 deletions

View File

@@ -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>

View File

@@ -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">

View File

@@ -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>