ERP-node/frontend/lib/utils/multilangLabelExtractor.ts

605 lines
20 KiB
TypeScript

/**
* 다국어 라벨 추출 유틸리티
* 화면 디자이너의 컴포넌트에서 다국어 처리가 필요한 라벨을 추출합니다.
*/
import { ComponentData } from "@/types/screen";
// 추출된 라벨 타입
export interface ExtractedLabel {
id: string;
componentId: string;
label: string;
type: "label" | "title" | "button" | "placeholder" | "column" | "filter" | "field" | "tab" | "action";
parentType?: string;
parentLabel?: string;
langKeyId?: number;
langKey?: string;
}
// 입력 폼 컴포넌트인지 확인
const INPUT_COMPONENT_TYPES = new Set([
"text-field",
"number-field",
"date-field",
"datetime-field",
"select-field",
"checkbox-field",
"radio-field",
"textarea-field",
"file-field",
"email-field",
"tel-field",
"password-field",
"entity-field",
"code-field",
"category-field",
"input-field",
"widget",
]);
const isInputComponent = (comp: any): boolean => {
const compType = comp.componentType || comp.type;
if (INPUT_COMPONENT_TYPES.has(compType)) return true;
if (compType === "widget" && comp.widgetType) return true;
if (comp.inputType || comp.webType) return true;
return false;
};
// 컬럼 라벨 맵 타입
export type ColumnLabelMap = Record<string, Record<string, string>>;
/**
* 컴포넌트에서 다국어 라벨 추출
* @param components 컴포넌트 배열
* @param columnLabelMap 테이블별 컬럼 라벨 맵 (선택사항)
* @returns 추출된 라벨 배열
*/
export function extractMultilangLabels(
components: ComponentData[],
columnLabelMap: ColumnLabelMap = {}
): ExtractedLabel[] {
const labels: ExtractedLabel[] = [];
const addedLabels = new Set<string>();
const addLabel = (
componentId: string,
label: string,
type: ExtractedLabel["type"],
parentType?: string,
parentLabel?: string,
langKeyId?: number,
langKey?: string
) => {
const key = `${componentId}_${type}_${label}`;
if (label && label.trim() && !addedLabels.has(key)) {
addedLabels.add(key);
labels.push({
id: key,
componentId,
label: label.trim(),
type,
parentType,
parentLabel,
langKeyId,
langKey,
});
}
};
const extractFromComponent = (comp: ComponentData, parentType?: string, parentLabel?: string) => {
const anyComp = comp as any;
const config = anyComp.componentConfig;
const compType = anyComp.componentType || anyComp.type;
const compLabel = anyComp.label || anyComp.title || compType;
// 1. 기본 라벨 - 입력 폼 컴포넌트인 경우에만 추출
if (isInputComponent(anyComp)) {
if (anyComp.label && typeof anyComp.label === "string") {
addLabel(comp.id, anyComp.label, "label", parentType, parentLabel, anyComp.langKeyId, anyComp.langKey);
}
}
// 2. 제목
if (anyComp.title && typeof anyComp.title === "string") {
addLabel(comp.id, anyComp.title, "title", parentType, parentLabel);
}
// 3. 버튼 텍스트 (componentId에 _button 접미사 추가하여 라벨과 구분)
if (config?.text && typeof config.text === "string") {
addLabel(
`${comp.id}_button`,
config.text,
"button",
parentType,
parentLabel,
config.langKeyId,
config.langKey
);
}
// 4. placeholder
if (anyComp.placeholder && typeof anyComp.placeholder === "string") {
addLabel(comp.id, anyComp.placeholder, "placeholder", parentType, parentLabel);
}
// 5. 테이블 컬럼 - columnLabelMap에서 한글 라벨 조회
const tableName = config?.selectedTable || config?.tableName || config?.table || anyComp.tableName;
if (config?.columns && Array.isArray(config.columns)) {
config.columns.forEach((col: any, index: number) => {
const colName = col.columnName || col.field || col.name;
// columnLabelMap에서 한글 라벨 조회, 없으면 displayName 사용
const colLabel = columnLabelMap[tableName]?.[colName] || col.displayName || col.label || colName;
if (colLabel && typeof colLabel === "string") {
addLabel(
`${comp.id}_col_${index}`,
colLabel,
"column",
compType,
compLabel,
col.langKeyId,
col.langKey
);
}
});
}
// 6. 분할 패널 제목 및 컬럼 - columnLabelMap에서 한글 라벨 조회
// 6-1. 좌측 패널 제목
if (config?.leftPanel?.title && typeof config.leftPanel.title === "string") {
addLabel(
`${comp.id}_left_title`,
config.leftPanel.title,
"title",
compType,
`${compLabel} (좌측)`,
config.leftPanel.langKeyId,
config.leftPanel.langKey
);
}
// 6-2. 우측 패널 제목
if (config?.rightPanel?.title && typeof config.rightPanel.title === "string") {
addLabel(
`${comp.id}_right_title`,
config.rightPanel.title,
"title",
compType,
`${compLabel} (우측)`,
config.rightPanel.langKeyId,
config.rightPanel.langKey
);
}
// 6-3. 좌측 패널 컬럼
const leftTableName = config?.leftPanel?.selectedTable || config?.leftPanel?.tableName || tableName;
if (config?.leftPanel?.columns && Array.isArray(config.leftPanel.columns)) {
config.leftPanel.columns.forEach((col: any, index: number) => {
const colName = col.columnName || col.field || col.name;
const colLabel = columnLabelMap[leftTableName]?.[colName] || col.displayName || col.label || colName;
if (colLabel && typeof colLabel === "string") {
addLabel(
`${comp.id}_left_col_${index}`,
colLabel,
"column",
compType,
`${compLabel} (좌측)`,
col.langKeyId,
col.langKey
);
}
});
}
const rightTableName = config?.rightPanel?.selectedTable || config?.rightPanel?.tableName || tableName;
if (config?.rightPanel?.columns && Array.isArray(config.rightPanel.columns)) {
config.rightPanel.columns.forEach((col: any, index: number) => {
const colName = col.columnName || col.field || col.name;
const colLabel = columnLabelMap[rightTableName]?.[colName] || col.displayName || col.label || colName;
if (colLabel && typeof colLabel === "string") {
addLabel(
`${comp.id}_right_col_${index}`,
colLabel,
"column",
compType,
`${compLabel} (우측)`,
col.langKeyId,
col.langKey
);
}
});
}
// 6-5. 추가 탭 (additionalTabs) 제목 및 컬럼 - rightPanel.additionalTabs 확인
const additionalTabs = config?.rightPanel?.additionalTabs || config?.additionalTabs;
if (additionalTabs && Array.isArray(additionalTabs)) {
additionalTabs.forEach((tab: any, tabIndex: number) => {
// 탭 라벨
if (tab.label && typeof tab.label === "string") {
addLabel(
`${comp.id}_addtab_${tabIndex}_label`,
tab.label,
"tab",
compType,
compLabel,
tab.langKeyId,
tab.langKey
);
}
// 탭 제목
if (tab.title && typeof tab.title === "string") {
addLabel(
`${comp.id}_addtab_${tabIndex}_title`,
tab.title,
"title",
compType,
`${compLabel} (탭: ${tab.label || tabIndex})`,
tab.titleLangKeyId,
tab.titleLangKey
);
}
// 탭 컬럼
const tabTableName = tab.tableName || tab.selectedTable || rightTableName;
if (tab.columns && Array.isArray(tab.columns)) {
tab.columns.forEach((col: any, colIndex: number) => {
const colName = col.columnName || col.field || col.name;
const colLabel = columnLabelMap[tabTableName]?.[colName] || col.displayName || col.label || colName;
if (colLabel && typeof colLabel === "string") {
addLabel(
`${comp.id}_addtab_${tabIndex}_col_${colIndex}`,
colLabel,
"column",
compType,
`${compLabel} (탭: ${tab.label || tabIndex})`,
col.langKeyId,
col.langKey
);
}
});
}
});
}
// 7. 검색 필터
if (config?.filter?.filters && Array.isArray(config.filter.filters)) {
config.filter.filters.forEach((filter: any, index: number) => {
if (filter.label && typeof filter.label === "string") {
addLabel(
`${comp.id}_filter_${index}`,
filter.label,
"filter",
compType,
compLabel,
filter.langKeyId,
filter.langKey
);
}
});
}
// 8. 폼 필드
if (config?.fields && Array.isArray(config.fields)) {
config.fields.forEach((field: any, index: number) => {
if (field.label && typeof field.label === "string") {
addLabel(
`${comp.id}_field_${index}`,
field.label,
"field",
compType,
compLabel,
field.langKeyId,
field.langKey
);
}
});
}
// 9. 탭
if (config?.tabs && Array.isArray(config.tabs)) {
config.tabs.forEach((tab: any, index: number) => {
if (tab.label && typeof tab.label === "string") {
addLabel(
`${comp.id}_tab_${index}`,
tab.label,
"tab",
compType,
compLabel,
tab.langKeyId,
tab.langKey
);
}
});
}
// 10. 액션 버튼
if (config?.actions?.actions && Array.isArray(config.actions.actions)) {
config.actions.actions.forEach((action: any, index: number) => {
if (action.label && typeof action.label === "string") {
addLabel(
`${comp.id}_action_${index}`,
action.label,
"action",
compType,
compLabel,
action.langKeyId,
action.langKey
);
}
});
}
// 자식 컴포넌트 재귀 탐색
if (anyComp.children && Array.isArray(anyComp.children)) {
anyComp.children.forEach((child: ComponentData) => {
extractFromComponent(child, compType, compLabel);
});
}
};
components.forEach((comp) => extractFromComponent(comp));
return labels;
}
/**
* 컴포넌트에서 테이블명 추출
* @param components 컴포넌트 배열
* @returns 테이블명 Set
*/
export function extractTableNames(components: ComponentData[]): Set<string> {
const tableNames = new Set<string>();
const extractTableName = (comp: any) => {
const config = comp.componentConfig;
// 1. 컴포넌트 직접 tableName
if (comp.tableName) tableNames.add(comp.tableName);
// 2. componentConfig 직접 tableName
if (config?.tableName) tableNames.add(config.tableName);
// 3. 테이블 리스트 컴포넌트 - selectedTable (주요!)
if (config?.selectedTable) tableNames.add(config.selectedTable);
// 4. 테이블 리스트 컴포넌트 - table 속성
if (config?.table) tableNames.add(config.table);
// 5. 분할 패널의 leftPanel/rightPanel
if (config?.leftPanel?.tableName) tableNames.add(config.leftPanel.tableName);
if (config?.rightPanel?.tableName) tableNames.add(config.rightPanel.tableName);
if (config?.leftPanel?.table) tableNames.add(config.leftPanel.table);
if (config?.rightPanel?.table) tableNames.add(config.rightPanel.table);
if (config?.leftPanel?.selectedTable) tableNames.add(config.leftPanel.selectedTable);
if (config?.rightPanel?.selectedTable) tableNames.add(config.rightPanel.selectedTable);
// 6. 검색 필터의 tableName
if (config?.filter?.tableName) tableNames.add(config.filter.tableName);
// 7. properties 안의 tableName
if (comp.properties?.tableName) tableNames.add(comp.properties.tableName);
if (comp.properties?.selectedTable) tableNames.add(comp.properties.selectedTable);
// 자식 컴포넌트 탐색
if (comp.children && Array.isArray(comp.children)) {
comp.children.forEach(extractTableName);
}
};
components.forEach(extractTableName);
return tableNames;
}
/**
* 다국어 키 매핑 결과를 컴포넌트에 적용
* @param components 원본 컴포넌트 배열
* @param mappings 다국어 키 매핑 결과 [{componentId, keyId, langKey}]
* @returns 업데이트된 컴포넌트 배열
*/
export function applyMultilangMappings(
components: ComponentData[],
mappings: Array<{ componentId: string; keyId: number; langKey: string }>
): ComponentData[] {
// 매핑을 빠르게 찾기 위한 맵 생성
const mappingMap = new Map(mappings.map((m) => [m.componentId, m]));
const updateComponent = (comp: ComponentData): ComponentData => {
const anyComp = comp as any;
const config = anyComp.componentConfig;
let updated = { ...comp } as any;
// 기본 컴포넌트 라벨 매핑 확인
const labelMapping = mappingMap.get(comp.id);
if (labelMapping) {
updated.langKeyId = labelMapping.keyId;
updated.langKey = labelMapping.langKey;
}
// 버튼 텍스트 매핑 (componentId_button 형식으로 조회)
const buttonMapping = mappingMap.get(`${comp.id}_button`);
if (buttonMapping && config?.text) {
updated.componentConfig = {
...updated.componentConfig,
langKeyId: buttonMapping.keyId,
langKey: buttonMapping.langKey,
};
}
// 컬럼 매핑
if (config?.columns && Array.isArray(config.columns)) {
const updatedColumns = config.columns.map((col: any, index: number) => {
const colMapping = mappingMap.get(`${comp.id}_col_${index}`);
if (colMapping) {
return { ...col, langKeyId: colMapping.keyId, langKey: colMapping.langKey };
}
return col;
});
updated.componentConfig = { ...config, columns: updatedColumns };
}
// 분할 패널 좌측 제목 매핑
const leftTitleMapping = mappingMap.get(`${comp.id}_left_title`);
if (leftTitleMapping && config?.leftPanel?.title) {
updated.componentConfig = {
...updated.componentConfig,
leftPanel: {
...updated.componentConfig?.leftPanel,
langKeyId: leftTitleMapping.keyId,
langKey: leftTitleMapping.langKey,
},
};
}
// 분할 패널 우측 제목 매핑
const rightTitleMapping = mappingMap.get(`${comp.id}_right_title`);
if (rightTitleMapping && config?.rightPanel?.title) {
updated.componentConfig = {
...updated.componentConfig,
rightPanel: {
...updated.componentConfig?.rightPanel,
langKeyId: rightTitleMapping.keyId,
langKey: rightTitleMapping.langKey,
},
};
}
// 분할 패널 좌측 컬럼 매핑 (이미 업데이트된 leftPanel 사용)
const currentLeftPanel = updated.componentConfig?.leftPanel || config?.leftPanel;
if (currentLeftPanel?.columns && Array.isArray(currentLeftPanel.columns)) {
const updatedLeftColumns = currentLeftPanel.columns.map((col: any, index: number) => {
const colMapping = mappingMap.get(`${comp.id}_left_col_${index}`);
if (colMapping) {
return { ...col, langKeyId: colMapping.keyId, langKey: colMapping.langKey };
}
return col;
});
updated.componentConfig = {
...updated.componentConfig,
leftPanel: { ...currentLeftPanel, columns: updatedLeftColumns },
};
}
// 분할 패널 우측 컬럼 매핑 (이미 업데이트된 rightPanel 사용)
const currentRightPanel = updated.componentConfig?.rightPanel || config?.rightPanel;
if (currentRightPanel?.columns && Array.isArray(currentRightPanel.columns)) {
const updatedRightColumns = currentRightPanel.columns.map((col: any, index: number) => {
const colMapping = mappingMap.get(`${comp.id}_right_col_${index}`);
if (colMapping) {
return { ...col, langKeyId: colMapping.keyId, langKey: colMapping.langKey };
}
return col;
});
updated.componentConfig = {
...updated.componentConfig,
rightPanel: { ...currentRightPanel, columns: updatedRightColumns },
};
}
// 필터 매핑
if (config?.filter?.filters && Array.isArray(config.filter.filters)) {
const updatedFilters = config.filter.filters.map((filter: any, index: number) => {
const filterMapping = mappingMap.get(`${comp.id}_filter_${index}`);
if (filterMapping) {
return { ...filter, langKeyId: filterMapping.keyId, langKey: filterMapping.langKey };
}
return filter;
});
updated.componentConfig = {
...updated.componentConfig,
filter: { ...config.filter, filters: updatedFilters },
};
}
// 폼 필드 매핑
if (config?.fields && Array.isArray(config.fields)) {
const updatedFields = config.fields.map((field: any, index: number) => {
const fieldMapping = mappingMap.get(`${comp.id}_field_${index}`);
if (fieldMapping) {
return { ...field, langKeyId: fieldMapping.keyId, langKey: fieldMapping.langKey };
}
return field;
});
updated.componentConfig = { ...updated.componentConfig, fields: updatedFields };
}
// 탭 매핑
if (config?.tabs && Array.isArray(config.tabs)) {
const updatedTabs = config.tabs.map((tab: any, index: number) => {
const tabMapping = mappingMap.get(`${comp.id}_tab_${index}`);
if (tabMapping) {
return { ...tab, langKeyId: tabMapping.keyId, langKey: tabMapping.langKey };
}
return tab;
});
updated.componentConfig = { ...updated.componentConfig, tabs: updatedTabs };
}
// 추가 탭 (additionalTabs) 매핑 - rightPanel.additionalTabs 또는 additionalTabs 확인
const currentRightPanelForAddTabs = updated.componentConfig?.rightPanel || config?.rightPanel;
const configAdditionalTabs = currentRightPanelForAddTabs?.additionalTabs || config?.additionalTabs;
if (configAdditionalTabs && Array.isArray(configAdditionalTabs)) {
const updatedAdditionalTabs = configAdditionalTabs.map((tab: any, tabIndex: number) => {
let updatedTab = { ...tab };
// 탭 라벨 매핑
const labelMapping = mappingMap.get(`${comp.id}_addtab_${tabIndex}_label`);
if (labelMapping) {
updatedTab.langKeyId = labelMapping.keyId;
updatedTab.langKey = labelMapping.langKey;
}
// 탭 제목 매핑
const titleMapping = mappingMap.get(`${comp.id}_addtab_${tabIndex}_title`);
if (titleMapping) {
updatedTab.titleLangKeyId = titleMapping.keyId;
updatedTab.titleLangKey = titleMapping.langKey;
}
// 탭 컬럼 매핑
if (tab.columns && Array.isArray(tab.columns)) {
updatedTab.columns = tab.columns.map((col: any, colIndex: number) => {
const colMapping = mappingMap.get(`${comp.id}_addtab_${tabIndex}_col_${colIndex}`);
if (colMapping) {
return { ...col, langKeyId: colMapping.keyId, langKey: colMapping.langKey };
}
return col;
});
}
return updatedTab;
});
// rightPanel.additionalTabs에 저장하거나 additionalTabs에 저장
if (currentRightPanelForAddTabs?.additionalTabs) {
updated.componentConfig = {
...updated.componentConfig,
rightPanel: { ...currentRightPanelForAddTabs, additionalTabs: updatedAdditionalTabs },
};
} else {
updated.componentConfig = { ...updated.componentConfig, additionalTabs: updatedAdditionalTabs };
}
}
// 액션 버튼 매핑
if (config?.actions?.actions && Array.isArray(config.actions.actions)) {
const updatedActions = config.actions.actions.map((action: any, index: number) => {
const actionMapping = mappingMap.get(`${comp.id}_action_${index}`);
if (actionMapping) {
return { ...action, langKeyId: actionMapping.keyId, langKey: actionMapping.langKey };
}
return action;
});
updated.componentConfig = {
...updated.componentConfig,
actions: { ...config.actions, actions: updatedActions },
};
}
// 자식 컴포넌트 재귀 처리
if (anyComp.children && Array.isArray(anyComp.children)) {
updated.children = anyComp.children.map((child: ComponentData) => updateComponent(child));
}
return updated;
};
return components.map(updateComponent);
}