메뉴관리 다국어 적용
This commit is contained in:
@@ -120,7 +120,6 @@ export const MenuManagement: React.FC = () => {
|
||||
"form.menu.type",
|
||||
"form.menu.type.admin",
|
||||
"form.menu.type.user",
|
||||
"form.status",
|
||||
"form.company",
|
||||
"form.company.select",
|
||||
"form.company.common",
|
||||
@@ -179,11 +178,123 @@ export const MenuManagement: React.FC = () => {
|
||||
// 초기 로딩
|
||||
useEffect(() => {
|
||||
loadCompanies();
|
||||
}, []); // 빈 의존성 배열로 한 번만 실행
|
||||
// 사용자 언어가 설정되지 않았을 때만 기본 텍스트 설정
|
||||
if (!userLang) {
|
||||
initializeDefaultTexts();
|
||||
}
|
||||
}, [userLang]); // userLang 변경 시마다 실행
|
||||
|
||||
// 초기 기본 텍스트 설정 함수
|
||||
const initializeDefaultTexts = () => {
|
||||
const defaultTexts: Record<string, string> = {};
|
||||
MENU_MANAGEMENT_LANG_KEYS.forEach((key) => {
|
||||
// 기본 한국어 텍스트 제공
|
||||
const defaultText = getDefaultText(key);
|
||||
defaultTexts[key] = defaultText;
|
||||
});
|
||||
setUiTexts(defaultTexts);
|
||||
console.log("🌐 초기 기본 텍스트 설정 완료:", Object.keys(defaultTexts).length);
|
||||
};
|
||||
|
||||
// 기본 텍스트 반환 함수
|
||||
const getDefaultText = (key: string): string => {
|
||||
const defaultTexts: Record<string, string> = {
|
||||
"menu.management.title": "메뉴 관리",
|
||||
"menu.management.description": "시스템의 메뉴 구조와 권한을 관리합니다.",
|
||||
"menu.type.title": "메뉴 타입",
|
||||
"menu.type.admin": "관리자",
|
||||
"menu.type.user": "사용자",
|
||||
"menu.management.admin": "관리자 메뉴",
|
||||
"menu.management.user": "사용자 메뉴",
|
||||
"menu.management.admin.description": "시스템 관리 및 설정 메뉴",
|
||||
"menu.management.user.description": "일반 사용자 업무 메뉴",
|
||||
"button.add": "추가",
|
||||
"button.add.top.level": "최상위 메뉴 추가",
|
||||
"button.add.sub": "하위 메뉴 추가",
|
||||
"button.edit": "수정",
|
||||
"button.delete": "삭제",
|
||||
"button.delete.selected": "선택 삭제",
|
||||
"button.delete.selected.count": "선택 삭제 ({count})",
|
||||
"button.delete.processing": "삭제 중...",
|
||||
"button.cancel": "취소",
|
||||
"button.save": "저장",
|
||||
"button.register": "등록",
|
||||
"button.modify": "수정",
|
||||
"filter.company": "회사",
|
||||
"filter.company.all": "전체",
|
||||
"filter.company.common": "공통",
|
||||
"filter.company.search": "회사 검색",
|
||||
"filter.search": "검색",
|
||||
"filter.search.placeholder": "메뉴명 또는 URL로 검색...",
|
||||
"filter.reset": "초기화",
|
||||
"table.header.select": "선택",
|
||||
"table.header.menu.name": "메뉴명",
|
||||
"table.header.menu.url": "URL",
|
||||
"table.header.menu.type": "메뉴 타입",
|
||||
"table.header.status": "상태",
|
||||
"table.header.company": "회사",
|
||||
"table.header.sequence": "순서",
|
||||
"table.header.actions": "작업",
|
||||
"status.active": "활성화",
|
||||
"status.inactive": "비활성화",
|
||||
"status.unspecified": "미지정",
|
||||
"form.menu.type": "메뉴 타입",
|
||||
"form.menu.type.admin": "관리자",
|
||||
"form.menu.type.user": "사용자",
|
||||
"form.company": "회사",
|
||||
"form.company.select": "회사를 선택하세요",
|
||||
"form.company.common": "공통",
|
||||
"form.company.submenu.note": "하위 메뉴는 상위 메뉴와 동일한 회사를 가져야 합니다.",
|
||||
"form.lang.key": "다국어 키",
|
||||
"form.lang.key.select": "다국어 키를 선택하세요",
|
||||
"form.lang.key.none": "다국어 키 없음",
|
||||
"form.lang.key.search": "다국어 키 검색...",
|
||||
"form.lang.key.selected": "선택된 키: {key} - {description}",
|
||||
"form.menu.name": "메뉴명",
|
||||
"form.menu.name.placeholder": "메뉴명을 입력하세요",
|
||||
"form.menu.url": "URL",
|
||||
"form.menu.url.placeholder": "메뉴 URL을 입력하세요",
|
||||
"form.menu.description": "설명",
|
||||
"form.menu.description.placeholder": "메뉴 설명을 입력하세요",
|
||||
"form.menu.sequence": "순서",
|
||||
"modal.menu.register.title": "메뉴 등록",
|
||||
"modal.menu.modify.title": "메뉴 수정",
|
||||
"modal.delete.title": "메뉴 삭제",
|
||||
"modal.delete.description": "해당 메뉴를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.",
|
||||
"modal.delete.batch.description":
|
||||
"선택된 {count}개의 메뉴를 영구적으로 삭제하시겠습니까?\n\n⚠️ 주의: 상위 메뉴를 삭제하면 하위 메뉴들도 함께 삭제됩니다.\n이 작업은 되돌릴 수 없습니다.",
|
||||
"message.loading": "로딩 중...",
|
||||
"message.menu.delete.processing": "메뉴 삭제 중...",
|
||||
"message.menu.save.success": "메뉴가 성공적으로 저장되었습니다.",
|
||||
"message.menu.save.failed": "메뉴 저장에 실패했습니다.",
|
||||
"message.menu.delete.success": "메뉴가 성공적으로 삭제되었습니다.",
|
||||
"message.menu.delete.failed": "메뉴 삭제에 실패했습니다.",
|
||||
"message.menu.delete.batch.success": "선택된 메뉴들이 성공적으로 삭제되었습니다.",
|
||||
"message.menu.delete.batch.partial": "일부 메뉴 삭제에 실패했습니다.",
|
||||
"message.menu.status.toggle.success": "메뉴 상태가 변경되었습니다.",
|
||||
"message.menu.status.toggle.failed": "메뉴 상태 변경에 실패했습니다.",
|
||||
"message.validation.menu.name.required": "메뉴명을 입력해주세요.",
|
||||
"message.validation.company.required": "회사를 선택해주세요.",
|
||||
"message.validation.select.menu.delete": "삭제할 메뉴를 선택해주세요.",
|
||||
"message.error.load.menu.list": "메뉴 목록을 불러오는데 실패했습니다.",
|
||||
"message.error.load.menu.info": "메뉴 정보를 불러오는데 실패했습니다.",
|
||||
"message.error.load.company.list": "회사 목록을 불러오는데 실패했습니다.",
|
||||
"message.error.load.lang.key.list": "다국어 키 목록을 불러오는데 실패했습니다.",
|
||||
"menu.list.title": "메뉴 목록",
|
||||
"menu.list.total": "총 {count}개",
|
||||
"menu.list.search.result": "검색 결과: {count}개",
|
||||
"ui.expand": "펼치기",
|
||||
"ui.collapse": "접기",
|
||||
"ui.menu.collapse": "메뉴 접기",
|
||||
"ui.language": "언어",
|
||||
};
|
||||
|
||||
return defaultTexts[key] || key;
|
||||
};
|
||||
|
||||
// 컴포넌트 마운트 시 및 userLang 변경 시 다국어 텍스트 로드
|
||||
useEffect(() => {
|
||||
if (!uiTextsLoading) {
|
||||
if (userLang && !uiTextsLoading) {
|
||||
loadUITexts();
|
||||
}
|
||||
}, [userLang]); // userLang 변경 시마다 실행
|
||||
@@ -199,17 +310,35 @@ export const MenuManagement: React.FC = () => {
|
||||
});
|
||||
}, [uiTexts]);
|
||||
|
||||
// 컴포넌트 마운트 시 강제로 번역 로드 (userLang이 아직 설정되지 않았을 수 있음)
|
||||
// 컴포넌트 마운트 후 다국어 텍스트 강제 로드 (userLang이 아직 설정되지 않았을 수 있음)
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (!uiTextsLoading && Object.keys(uiTexts).length === 0) {
|
||||
console.log("🔄 컴포넌트 마운트 후 강제 번역 로드");
|
||||
if (userLang && !uiTextsLoading) {
|
||||
console.log("🔄 컴포넌트 마운트 후 다국어 텍스트 강제 로드");
|
||||
loadUITexts();
|
||||
}
|
||||
}, 100); // 100ms 후 실행
|
||||
}, 300); // 300ms 후 실행
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []); // 컴포넌트 마운트 시 한 번만 실행
|
||||
}, [userLang]); // userLang이 설정된 후 실행
|
||||
|
||||
// 추가 안전장치: 컴포넌트 마운트 후 일정 시간이 지나면 강제로 다국어 텍스트 로드
|
||||
useEffect(() => {
|
||||
const fallbackTimer = setTimeout(() => {
|
||||
if (!uiTextsLoading && Object.keys(uiTexts).length === 0) {
|
||||
console.log("🔄 안전장치: 컴포넌트 마운트 후 강제 다국어 텍스트 로드");
|
||||
// 사용자 언어가 설정되지 않았을 때만 기본 텍스트 설정
|
||||
if (!userLang) {
|
||||
initializeDefaultTexts();
|
||||
} else {
|
||||
// 사용자 언어가 설정된 경우 다국어 텍스트 로드
|
||||
loadUITexts();
|
||||
}
|
||||
}
|
||||
}, 1000); // 1초 후 실행
|
||||
|
||||
return () => clearTimeout(fallbackTimer);
|
||||
}, [userLang]); // userLang 변경 시마다 실행
|
||||
|
||||
// 번역 로드 이벤트 감지
|
||||
useEffect(() => {
|
||||
@@ -293,12 +422,22 @@ export const MenuManagement: React.FC = () => {
|
||||
console.log("🌐 사용자 언어가 설정되지 않음, 기본값 설정");
|
||||
const defaultTexts: Record<string, string> = {};
|
||||
MENU_MANAGEMENT_LANG_KEYS.forEach((key) => {
|
||||
defaultTexts[key] = key; // 키를 기본값으로 사용
|
||||
defaultTexts[key] = getDefaultText(key); // 기본 한국어 텍스트 사용
|
||||
});
|
||||
setUiTexts(defaultTexts);
|
||||
return;
|
||||
}
|
||||
|
||||
// 사용자 언어가 설정된 경우, 기존 uiTexts가 비어있으면 기본 텍스트로 초기화
|
||||
if (Object.keys(uiTexts).length === 0) {
|
||||
console.log("🌐 기존 uiTexts가 비어있음, 기본 텍스트로 초기화");
|
||||
const defaultTexts: Record<string, string> = {};
|
||||
MENU_MANAGEMENT_LANG_KEYS.forEach((key) => {
|
||||
defaultTexts[key] = getDefaultText(key);
|
||||
});
|
||||
setUiTexts(defaultTexts);
|
||||
}
|
||||
|
||||
console.log("🌐 UI 다국어 텍스트 로드 시작", {
|
||||
userLang,
|
||||
apiParams: {
|
||||
@@ -328,32 +467,26 @@ export const MenuManagement: React.FC = () => {
|
||||
const translations = response.data.data;
|
||||
console.log("🌐 배치 다국어 텍스트 응답:", translations);
|
||||
|
||||
// 번역 결과를 상태에 저장
|
||||
console.log("🔧 setUiTexts 호출 전:", { translationsCount: Object.keys(translations).length });
|
||||
setUiTexts(translations);
|
||||
console.log("🔧 setUiTexts 호출 후 - translations:", translations);
|
||||
// 번역 결과를 상태에 저장 (기존 uiTexts와 병합)
|
||||
const mergedTranslations = { ...uiTexts, ...translations };
|
||||
console.log("🔧 setUiTexts 호출 전:", {
|
||||
translationsCount: Object.keys(translations).length,
|
||||
mergedCount: Object.keys(mergedTranslations).length,
|
||||
});
|
||||
setUiTexts(mergedTranslations);
|
||||
console.log("🔧 setUiTexts 호출 후 - mergedTranslations:", mergedTranslations);
|
||||
|
||||
// 번역 캐시에 저장 (다른 컴포넌트에서도 사용할 수 있도록)
|
||||
setTranslationCache(userLang, translations);
|
||||
setTranslationCache(userLang, mergedTranslations);
|
||||
} else {
|
||||
console.error("❌ 다국어 텍스트 배치 조회 실패:", response.data.message);
|
||||
|
||||
// API 실패 시 기본 텍스트 사용
|
||||
const defaultTexts: Record<string, string> = {};
|
||||
MENU_MANAGEMENT_LANG_KEYS.forEach((key) => {
|
||||
defaultTexts[key] = key; // 키를 기본값으로 사용
|
||||
});
|
||||
setUiTexts(defaultTexts);
|
||||
// API 실패 시에도 기존 uiTexts는 유지
|
||||
console.log("🔄 API 실패로 인해 기존 uiTexts 유지");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ UI 다국어 텍스트 로드 실패:", error);
|
||||
|
||||
// API 실패 시 기본 텍스트 사용
|
||||
const defaultTexts: Record<string, string> = {};
|
||||
MENU_MANAGEMENT_LANG_KEYS.forEach((key) => {
|
||||
defaultTexts[key] = key; // 키를 기본값으로 사용
|
||||
});
|
||||
setUiTexts(defaultTexts);
|
||||
// API 실패 시에도 기존 uiTexts는 유지
|
||||
console.log("🔄 API 실패로 인해 기존 uiTexts 유지");
|
||||
} finally {
|
||||
setUiTextsLoading(false);
|
||||
}
|
||||
@@ -367,16 +500,9 @@ export const MenuManagement: React.FC = () => {
|
||||
// uiTexts에서 번역 텍스트 찾기
|
||||
let text = uiTexts[key];
|
||||
|
||||
// 디버깅: uiTexts 상태 확인
|
||||
// uiTexts에 없으면 fallback 또는 키 사용
|
||||
if (!text) {
|
||||
console.log(`🔍 getUITextSync - 키 "${key}"를 uiTexts에서 찾을 수 없음`);
|
||||
console.log("🔍 uiTexts 상태:", {
|
||||
count: Object.keys(uiTexts).length,
|
||||
sampleKeys: Object.keys(uiTexts).slice(0, 5),
|
||||
});
|
||||
text = fallback || key;
|
||||
} else {
|
||||
console.log(`✅ getUITextSync - 키 "${key}" 번역 텍스트 찾음: "${text}"`);
|
||||
}
|
||||
|
||||
// 파라미터 치환
|
||||
@@ -900,6 +1026,7 @@ export const MenuManagement: React.FC = () => {
|
||||
onSelectAllMenus={handleSelectAllMenus}
|
||||
expandedMenus={expandedMenus}
|
||||
onToggleExpand={handleToggleExpand}
|
||||
uiTexts={uiTexts}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -915,6 +1042,7 @@ export const MenuManagement: React.FC = () => {
|
||||
menuType={formData.menuType}
|
||||
level={formData.level}
|
||||
parentCompanyCode={formData.parentCompanyCode}
|
||||
uiTexts={uiTexts}
|
||||
/>
|
||||
|
||||
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||
|
||||
Reference in New Issue
Block a user