메뉴관리 다국어 적용

This commit is contained in:
kjs 2025-08-25 17:56:33 +09:00
parent 307faba089
commit 7bb7f1621f
4 changed files with 241 additions and 127 deletions

View File

@ -10,7 +10,7 @@ import { Textarea } from "@/components/ui/textarea";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { toast } from "sonner";
import { getMenuTextSync, MENU_MANAGEMENT_KEYS, setTranslationCache } from "@/lib/utils/multilang";
import { MENU_MANAGEMENT_KEYS } from "@/lib/utils/multilang";
interface Company {
company_code: string;
@ -27,6 +27,8 @@ interface MenuFormModalProps {
menuType?: string;
level?: number;
parentCompanyCode?: string;
// 다국어 텍스트 props 추가
uiTexts: Record<string, string>;
}
export const MenuFormModal: React.FC<MenuFormModalProps> = ({
@ -38,6 +40,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
menuType,
level,
parentCompanyCode,
uiTexts,
}) => {
console.log("🎯 MenuFormModal 렌더링 - Props:", {
isOpen,
@ -48,6 +51,11 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
parentCompanyCode,
});
// 다국어 텍스트 가져오기 함수
const getText = (key: string, fallback?: string): string => {
return uiTexts[key] || fallback || key;
};
console.log("🔍 MenuFormModal 컴포넌트 마운트됨");
const [formData, setFormData] = useState<MenuFormData>({
@ -149,7 +157,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
stack: error?.stack,
response: error?.response,
});
toast.error(getMenuTextSync(MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_MENU_INFO));
toast.error(getText(MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_MENU_INFO));
} finally {
setLoading(false);
}
@ -254,7 +262,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
setCompanies(companyList);
} catch (error) {
console.error("회사 목록 로딩 오류:", error);
toast.error(getMenuTextSync(MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_COMPANY_LIST));
toast.error(getText(MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_COMPANY_LIST));
}
};
@ -273,7 +281,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
}
} catch (error) {
console.error("❌ 다국어 키 목록 로딩 오류:", error);
toast.error(getMenuTextSync(MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_LANG_KEY_LIST));
toast.error(getText(MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_LANG_KEY_LIST));
setLangKeys([]);
}
};
@ -282,12 +290,12 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
e.preventDefault();
if (!formData.menuNameKor.trim()) {
toast.error(getMenuTextSync(MENU_MANAGEMENT_KEYS.MESSAGE_VALIDATION_MENU_NAME_REQUIRED));
toast.error(getText(MENU_MANAGEMENT_KEYS.MESSAGE_VALIDATION_MENU_NAME_REQUIRED));
return;
}
if (!formData.companyCode) {
toast.error(getMenuTextSync(MENU_MANAGEMENT_KEYS.MESSAGE_VALIDATION_COMPANY_REQUIRED));
toast.error(getText(MENU_MANAGEMENT_KEYS.MESSAGE_VALIDATION_COMPANY_REQUIRED));
return;
}
@ -324,7 +332,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
}
} catch (error) {
console.error("메뉴 저장/수정 실패:", error);
toast.error(getMenuTextSync(MENU_MANAGEMENT_KEYS.MESSAGE_MENU_SAVE_FAILED));
toast.error(getText(MENU_MANAGEMENT_KEYS.MESSAGE_MENU_SAVE_FAILED));
} finally {
setLoading(false);
}
@ -356,68 +364,52 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
<DialogHeader>
<DialogTitle>
{isEdit
? getMenuTextSync(MENU_MANAGEMENT_KEYS.MODAL_MENU_MODIFY_TITLE, getCurrentUserLang())
: getMenuTextSync(MENU_MANAGEMENT_KEYS.MODAL_MENU_REGISTER_TITLE, getCurrentUserLang())}
? getText(MENU_MANAGEMENT_KEYS.MODAL_MENU_MODIFY_TITLE)
: getText(MENU_MANAGEMENT_KEYS.MODAL_MENU_REGISTER_TITLE)}
</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="menuType">
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_TYPE, getCurrentUserLang())}
</Label>
<Label htmlFor="menuType">{getText(MENU_MANAGEMENT_KEYS.FORM_MENU_TYPE)}</Label>
<Select value={formData.menuType} onValueChange={(value) => handleInputChange("menuType", value)}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="0">
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_TYPE_ADMIN, getCurrentUserLang())}
</SelectItem>
<SelectItem value="1">
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_TYPE_USER, getCurrentUserLang())}
</SelectItem>
<SelectItem value="0">{getText(MENU_MANAGEMENT_KEYS.FORM_MENU_TYPE_ADMIN)}</SelectItem>
<SelectItem value="1">{getText(MENU_MANAGEMENT_KEYS.FORM_MENU_TYPE_USER)}</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="status">{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_STATUS, getCurrentUserLang())}</Label>
<Label htmlFor="status">{getText(MENU_MANAGEMENT_KEYS.TABLE_HEADER_STATUS)}</Label>
<Select value={formData.status} onValueChange={(value) => handleInputChange("status", value)}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="ACTIVE">
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_STATUS_ACTIVE, getCurrentUserLang())}
</SelectItem>
<SelectItem value="INACTIVE">
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_STATUS_INACTIVE, getCurrentUserLang())}
</SelectItem>
<SelectItem value="ACTIVE">{getText(MENU_MANAGEMENT_KEYS.STATUS_ACTIVE)}</SelectItem>
<SelectItem value="INACTIVE">{getText(MENU_MANAGEMENT_KEYS.STATUS_INACTIVE)}</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="companyCode">
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_COMPANY, getCurrentUserLang())} *
</Label>
<Label htmlFor="companyCode">{getText(MENU_MANAGEMENT_KEYS.FORM_COMPANY)} *</Label>
<Select
value={formData.companyCode}
onValueChange={(value) => handleInputChange("companyCode", value)}
disabled={!isEdit && level !== 1} // 수정 모드가 아니고 최상위 메뉴가 아니면 비활성화
>
<SelectTrigger>
<SelectValue
placeholder={getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_COMPANY_SELECT, getCurrentUserLang())}
/>
<SelectValue placeholder={getText(MENU_MANAGEMENT_KEYS.FORM_COMPANY_SELECT)} />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_COMPANY_COMMON, getCurrentUserLang())}
</SelectItem>
<SelectItem value="none">{getText(MENU_MANAGEMENT_KEYS.FORM_COMPANY_COMMON)}</SelectItem>
{companies.map((company) => (
<SelectItem key={company.company_code} value={company.company_code}>
{company.company_name}
@ -426,14 +418,12 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
</SelectContent>
</Select>
{!isEdit && level !== 1 && (
<p className="text-xs text-gray-500">
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_COMPANY_SUBMENU_NOTE, getCurrentUserLang())}
</p>
<p className="text-xs text-gray-500">{getText(MENU_MANAGEMENT_KEYS.FORM_COMPANY_SUBMENU_NOTE)}</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="langKey">{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY, getCurrentUserLang())}</Label>
<Label htmlFor="langKey">{getText(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY)}</Label>
<div className="langkey-dropdown relative">
<button
type="button"
@ -442,7 +432,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
disabled={!formData.companyCode}
>
<span className={!formData.langKey ? "text-muted-foreground" : ""}>
{formData.langKey || getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_SELECT, getCurrentUserLang())}
{formData.langKey || getText(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_SELECT)}
</span>
<svg
className={`h-4 w-4 transition-transform ${isLangKeyDropdownOpen ? "rotate-180" : ""}`}
@ -459,7 +449,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
{/* 검색 입력 */}
<div className="border-b p-2">
<Input
placeholder={getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_SEARCH, getCurrentUserLang())}
placeholder={getText(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_SEARCH)}
value={langKeySearchText}
onChange={(e) => setLangKeySearchText(e.target.value)}
className="h-8 text-sm"
@ -477,7 +467,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
setLangKeySearchText("");
}}
>
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_NONE, getCurrentUserLang())}
{getText(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_NONE)}
</div>
{langKeys
@ -506,57 +496,47 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
</div>
{selectedLangKeyInfo && (
<p className="text-xs text-gray-500">
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_SELECTED, {
key: selectedLangKeyInfo.langKey,
description: selectedLangKeyInfo.description,
})}
{getText(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_SELECTED)
.replace("{key}", selectedLangKeyInfo.langKey)
.replace("{description}", selectedLangKeyInfo.description)}
</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="menuNameKor">
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_NAME, getCurrentUserLang())} *
</Label>
<Label htmlFor="menuNameKor">{getText(MENU_MANAGEMENT_KEYS.FORM_MENU_NAME)} *</Label>
<Input
id="menuNameKor"
value={formData.menuNameKor}
onChange={(e) => handleInputChange("menuNameKor", e.target.value)}
placeholder={getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_NAME_PLACEHOLDER, getCurrentUserLang())}
placeholder={getText(MENU_MANAGEMENT_KEYS.FORM_MENU_NAME_PLACEHOLDER)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="menuUrl">{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_URL, getCurrentUserLang())}</Label>
<Label htmlFor="menuUrl">{getText(MENU_MANAGEMENT_KEYS.FORM_MENU_URL)}</Label>
<Input
id="menuUrl"
value={formData.menuUrl}
onChange={(e) => handleInputChange("menuUrl", e.target.value)}
placeholder={getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_URL_PLACEHOLDER, getCurrentUserLang())}
placeholder={getText(MENU_MANAGEMENT_KEYS.FORM_MENU_URL_PLACEHOLDER)}
/>
</div>
<div className="space-y-2">
<Label htmlFor="menuDesc">
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_DESCRIPTION, getCurrentUserLang())}
</Label>
<Label htmlFor="menuDesc">{getText(MENU_MANAGEMENT_KEYS.FORM_MENU_DESCRIPTION)}</Label>
<Textarea
id="menuDesc"
value={formData.menuDesc}
onChange={(e) => handleInputChange("menuDesc", e.target.value)}
placeholder={getMenuTextSync(
MENU_MANAGEMENT_KEYS.FORM_MENU_DESCRIPTION_PLACEHOLDER,
getCurrentUserLang(),
)}
placeholder={getText(MENU_MANAGEMENT_KEYS.FORM_MENU_DESCRIPTION_PLACEHOLDER)}
rows={3}
/>
</div>
<div className="space-y-2">
<Label htmlFor="seq">
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_SEQUENCE, getCurrentUserLang())}
</Label>
<Label htmlFor="seq">{getText(MENU_MANAGEMENT_KEYS.FORM_MENU_SEQUENCE)}</Label>
<Input
id="seq"
type="number"
@ -568,14 +548,14 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
<div className="flex justify-end space-x-2 pt-4">
<Button type="button" variant="outline" onClick={onClose}>
{getMenuTextSync(MENU_MANAGEMENT_KEYS.BUTTON_CANCEL, getCurrentUserLang())}
{getText(MENU_MANAGEMENT_KEYS.BUTTON_CANCEL)}
</Button>
<Button type="submit" disabled={loading}>
{loading
? getMenuTextSync(MENU_MANAGEMENT_KEYS.BUTTON_SAVE_PROCESSING, getCurrentUserLang())
? getText(MENU_MANAGEMENT_KEYS.BUTTON_SAVE_PROCESSING)
: isEdit
? getMenuTextSync(MENU_MANAGEMENT_KEYS.BUTTON_MODIFY, getCurrentUserLang())
: getMenuTextSync(MENU_MANAGEMENT_KEYS.BUTTON_REGISTER, getCurrentUserLang())}
? getText(MENU_MANAGEMENT_KEYS.BUTTON_MODIFY)
: getText(MENU_MANAGEMENT_KEYS.BUTTON_REGISTER)}
</Button>
</div>
</form>

View File

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

View File

@ -6,8 +6,8 @@ import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { toast } from "sonner";
import { useMultiLang } from "@/hooks/useMultiLang";
import { getMenuTextSync, MENU_MANAGEMENT_KEYS, setTranslationCache } from "@/lib/utils/multilang";
import { MENU_MANAGEMENT_KEYS } from "@/lib/utils/multilang";
interface MenuTableProps {
menus: MenuItem[];
@ -20,6 +20,8 @@ interface MenuTableProps {
onSelectAllMenus: (checked: boolean) => void;
expandedMenus: Set<string>;
onToggleExpand: (menuId: string) => void;
// 다국어 텍스트 props 추가
uiTexts: Record<string, string>;
}
export const MenuTable: React.FC<MenuTableProps> = ({
@ -33,8 +35,12 @@ export const MenuTable: React.FC<MenuTableProps> = ({
onSelectAllMenus,
expandedMenus,
onToggleExpand,
uiTexts,
}) => {
const { userLang } = useMultiLang();
// 다국어 텍스트 가져오기 함수
const getText = (key: string, fallback?: string): string => {
return uiTexts[key] || fallback || key;
};
// 다국어 텍스트 표시 함수 (기본값 처리)
const getDisplayText = (menu: MenuItem) => {
@ -130,8 +136,8 @@ export const MenuTable: React.FC<MenuTableProps> = ({
}`}
>
{status === "active"
? getMenuTextSync(MENU_MANAGEMENT_KEYS.STATUS_ACTIVE)
: getMenuTextSync(MENU_MANAGEMENT_KEYS.STATUS_INACTIVE)}
? getText(MENU_MANAGEMENT_KEYS.STATUS_ACTIVE)
: getText(MENU_MANAGEMENT_KEYS.STATUS_INACTIVE)}
</button>
);
};
@ -156,22 +162,22 @@ export const MenuTable: React.FC<MenuTableProps> = ({
/>
</TableHead>
<TableHead className="w-1/3 bg-gray-50 font-semibold text-gray-700">
{getMenuTextSync(MENU_MANAGEMENT_KEYS.TABLE_HEADER_MENU_NAME)}
{getText(MENU_MANAGEMENT_KEYS.TABLE_HEADER_MENU_NAME)}
</TableHead>
<TableHead className="w-16 bg-gray-50 font-semibold text-gray-700">
{getMenuTextSync(MENU_MANAGEMENT_KEYS.TABLE_HEADER_SEQUENCE)}
{getText(MENU_MANAGEMENT_KEYS.TABLE_HEADER_SEQUENCE)}
</TableHead>
<TableHead className="w-24 bg-gray-50 font-semibold text-gray-700">
{getMenuTextSync(MENU_MANAGEMENT_KEYS.TABLE_HEADER_COMPANY)}
{getText(MENU_MANAGEMENT_KEYS.TABLE_HEADER_COMPANY)}
</TableHead>
<TableHead className="w-48 bg-gray-50 font-semibold text-gray-700">
{getMenuTextSync(MENU_MANAGEMENT_KEYS.TABLE_HEADER_MENU_URL)}
{getText(MENU_MANAGEMENT_KEYS.TABLE_HEADER_MENU_URL)}
</TableHead>
<TableHead className="w-20 bg-gray-50 font-semibold text-gray-700">
{getMenuTextSync(MENU_MANAGEMENT_KEYS.TABLE_HEADER_STATUS)}
{getText(MENU_MANAGEMENT_KEYS.TABLE_HEADER_STATUS)}
</TableHead>
<TableHead className="w-32 bg-gray-50 font-semibold text-gray-700">
{getMenuTextSync(MENU_MANAGEMENT_KEYS.TABLE_HEADER_ACTIONS)}
{getText(MENU_MANAGEMENT_KEYS.TABLE_HEADER_ACTIONS)}
</TableHead>
</TableRow>
</TableHeader>
@ -236,11 +242,11 @@ export const MenuTable: React.FC<MenuTableProps> = ({
<TableCell className="text-sm text-gray-600">
<div className="flex flex-col">
<span
className={`font-medium ${companyName && companyName !== getMenuTextSync(MENU_MANAGEMENT_KEYS.STATUS_UNSPECIFIED) ? "text-green-600" : "text-gray-500"}`}
className={`font-medium ${companyName && companyName !== getText(MENU_MANAGEMENT_KEYS.STATUS_UNSPECIFIED) ? "text-green-600" : "text-gray-500"}`}
>
{companyCode === "*"
? getMenuTextSync(MENU_MANAGEMENT_KEYS.FILTER_COMPANY_COMMON)
: companyName || getMenuTextSync(MENU_MANAGEMENT_KEYS.STATUS_UNSPECIFIED)}
? getText(MENU_MANAGEMENT_KEYS.FILTER_COMPANY_COMMON)
: companyName || getText(MENU_MANAGEMENT_KEYS.STATUS_UNSPECIFIED)}
</span>
{companyCode && companyCode !== "" && (
<span className="font-mono text-xs text-gray-400">{companyCode}</span>
@ -285,7 +291,7 @@ export const MenuTable: React.FC<MenuTableProps> = ({
className="min-w-[40px] px-1 py-1 text-xs"
onClick={() => onAddMenu(objid, menuType, lev)}
>
{getMenuTextSync(MENU_MANAGEMENT_KEYS.BUTTON_ADD)}
{getText(MENU_MANAGEMENT_KEYS.BUTTON_ADD)}
</Button>
)}
{lev === 2 && (
@ -296,7 +302,7 @@ export const MenuTable: React.FC<MenuTableProps> = ({
className="min-w-[40px] px-1 py-1 text-xs"
onClick={() => onAddMenu(objid, menuType, lev)}
>
{getMenuTextSync(MENU_MANAGEMENT_KEYS.BUTTON_ADD_SUB)}
{getText(MENU_MANAGEMENT_KEYS.BUTTON_ADD_SUB)}
</Button>
<Button
size="sm"
@ -304,7 +310,7 @@ export const MenuTable: React.FC<MenuTableProps> = ({
className="min-w-[40px] px-1 py-1 text-xs"
onClick={() => onEditMenu(objid)}
>
{getMenuTextSync(MENU_MANAGEMENT_KEYS.BUTTON_EDIT)}
{getText(MENU_MANAGEMENT_KEYS.BUTTON_EDIT)}
</Button>
</>
)}
@ -315,7 +321,7 @@ export const MenuTable: React.FC<MenuTableProps> = ({
className="min-w-[40px] px-1 py-1 text-xs"
onClick={() => onEditMenu(objid)}
>
{getMenuTextSync(MENU_MANAGEMENT_KEYS.BUTTON_EDIT)}
{getText(MENU_MANAGEMENT_KEYS.BUTTON_EDIT)}
</Button>
)}
</div>

View File

@ -60,7 +60,7 @@ export const useMultiLang = (options: { companyCode?: string } = {}) => {
// 전역 로케일이 아직 로드되지 않았으면 대기
console.log("⏳ 전역 로케일 로드 대기 중...");
// 주기적으로 전역 로케일 확인
// 주기적으로 전역 로케일 확인 (더 빠른 간격으로)
const checkInterval = setInterval(() => {
if ((window as any).__GLOBAL_USER_LOCALE_LOADED) {
const globalLocale = (window as any).__GLOBAL_USER_LANG;
@ -69,9 +69,9 @@ export const useMultiLang = (options: { companyCode?: string } = {}) => {
globalUserLang = globalLocale;
clearInterval(checkInterval);
}
}, 100);
}, 50); // 50ms로 단축
// 5초 후 타임아웃
// 3초 후 타임아웃 (더 빠른 타임아웃)
setTimeout(() => {
clearInterval(checkInterval);
if (!userLang) {
@ -79,14 +79,14 @@ export const useMultiLang = (options: { companyCode?: string } = {}) => {
setUserLang("KR");
globalUserLang = "KR";
}
}, 5000);
}, 3000); // 3초로 단축
return () => clearInterval(checkInterval);
}, []);
// 다국어 텍스트 가져오기 (배치 조회 방식)
const getText = async (menuCode: string, langKey: string, fallback?: string): Promise<string> => {
console.log(`🔍 다국어 텍스트 요청 (배치 방식):`, { menuCode, langKey, userLang, companyCode });
console.log("🔍 다국어 텍스트 요청 (배치 방식):", { menuCode, langKey, userLang, companyCode });
try {
// 배치 조회 API 사용
@ -104,7 +104,7 @@ export const useMultiLang = (options: { companyCode?: string } = {}) => {
},
);
console.log(`📡 배치 API 응답 상태:`, response.status, response.statusText);
console.log("📡 배치 API 응답 상태:", response.status, response.statusText);
if (response.data.success && response.data.data && response.data.data[langKey]) {
// 번역 텍스트를 캐시에 저장
@ -117,11 +117,11 @@ export const useMultiLang = (options: { companyCode?: string } = {}) => {
}
// 실패 시 fallback 또는 키 반환
console.log(`🔄 배치 API 성공했지만 데이터 없음, fallback 반환:`, fallback || langKey);
console.log("🔄 배치 API 성공했지만 데이터 없음, fallback 반환:", fallback || langKey);
return fallback || langKey;
} catch (error) {
console.error("❌ 다국어 텍스트 배치 조회 실패:", error);
console.log(`🔄 에러 시 fallback 반환:`, fallback || langKey);
console.log("🔄 에러 시 fallback 반환:", fallback || langKey);
return fallback || langKey;
}
};