Merge pull request '사용자 언어 변경 시 다국어 적용 즉시 적용' (#12) from sidebar/i18n into dev

Reviewed-on: http://39.117.244.52:3000/kjs/ERP-node/pulls/12
This commit is contained in:
hyeonsu 2025-09-01 17:18:37 +09:00
commit 94ec47afe7
3 changed files with 57 additions and 27 deletions

View File

@ -18,13 +18,12 @@ export function LanguageSelector({ className }: LanguageSelectorProps) {
]; ];
const handleLanguageChange = (newLang: string) => { const handleLanguageChange = (newLang: string) => {
console.log("🎯 LanguageSelector 언어 변경:", newLang);
changeLang(newLang); changeLang(newLang);
// 언어 변경 시 메뉴 컨텍스트가 자동으로 새로고침됨
console.log("언어 변경됨:", newLang);
}; };
return ( return (
<Select value={userLang} onValueChange={handleLanguageChange}> <Select value={userLang || ""} onValueChange={handleLanguageChange}>
<SelectTrigger className={`${className}`}> <SelectTrigger className={`${className}`}>
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>

View File

@ -5,17 +5,51 @@ import { apiClient } from "@/lib/api/client";
let globalUserLang = "KR"; let globalUserLang = "KR";
let globalChangeLangCallback: ((lang: string) => void) | null = null; let globalChangeLangCallback: ((lang: string) => void) | null = null;
// 🎯 효율적인 언어 변경 알림 시스템
const languageChangeCallbacks = new Set<(newLang: string) => void>();
// 전역 언어 변경 함수 (외부에서 호출 가능하도록 export)
export const notifyLanguageChange = (newLang: string) => {
globalUserLang = newLang;
(window as any).__GLOBAL_USER_LANG = newLang;
localStorage.setItem("userLocale", newLang);
localStorage.setItem("userLocaleLoaded", "true");
(window as any).__GLOBAL_USER_LOCALE_LOADED = true;
// 🗑️ 번역 캐시 초기화
(window as any).__TRANSLATION_CACHE = {};
// 모든 컴포넌트에 즉시 알림 (폴링 없이!)
languageChangeCallbacks.forEach((callback) => callback(newLang));
console.log("🔄 모든 컴포넌트에 언어 변경 알림:", newLang);
};
export const useMultiLang = (options: { companyCode?: string } = {}) => { export const useMultiLang = (options: { companyCode?: string } = {}) => {
const [userLang, setUserLang] = useState<string | null>(null); // null로 시작 const [userLang, setUserLang] = useState<string | null>(null); // null로 시작
const companyCode = options.companyCode || "*"; const companyCode = options.companyCode || "*";
// 전역 언어 상태 동기화 (무한 루프 방지) // 🎯 효율적인 언어 변경 감지 (폴링 대신 콜백 등록)
useEffect(() => { useEffect(() => {
// 초기 로딩 시에만 동기화 // 언어 변경 콜백 등록
if (globalUserLang && globalUserLang !== userLang) { const handleLanguageChange = (newLang: string) => {
setUserLang(globalUserLang); console.log("🔄 언어 변경 감지 (즉시):", { from: userLang, to: newLang });
setUserLang(newLang);
};
languageChangeCallbacks.add(handleLanguageChange);
// 초기 언어 설정
const currentLang = (window as any).__GLOBAL_USER_LANG || localStorage.getItem("userLocale") || globalUserLang;
if (currentLang && currentLang !== userLang) {
setUserLang(currentLang);
} }
}, []); // 의존성 배열을 비워서 한 번만 실행
// 클린업: 콜백 제거
return () => {
languageChangeCallbacks.delete(handleLanguageChange);
};
}, []); // 한 번만 등록
// 언어 변경 시 전역 콜백 호출 (무한 루프 방지) // 언어 변경 시 전역 콜백 호출 (무한 루프 방지)
useEffect(() => { useEffect(() => {
@ -126,8 +160,10 @@ export const useMultiLang = (options: { companyCode?: string } = {}) => {
} }
}; };
// 언어 변경 (무한 루프 방지) // 🎯 효율적인 언어 변경 함수 (콜백 방식)
const changeLang = async (newLang: string) => { const changeLang = async (newLang: string) => {
console.log("🚀 useMultiLang changeLang 호출:", { newLang, userLang });
// 같은 언어로 변경하려는 경우 무시 // 같은 언어로 변경하려는 경우 무시
if (newLang === userLang) { if (newLang === userLang) {
console.log("🔄 같은 언어로 변경 시도 무시:", newLang); console.log("🔄 같은 언어로 변경 시도 무시:", newLang);
@ -143,19 +179,17 @@ export const useMultiLang = (options: { companyCode?: string } = {}) => {
}); });
if (response.data.success) { if (response.data.success) {
// 전역 상태 먼저 업데이트 // 🎯 핵심: 즉시 모든 컴포넌트에 알림 (폴링 없이!)
globalUserLang = newLang; notifyLanguageChange(newLang);
// 로컬 상태 업데이트
setUserLang(newLang); console.log("✅ 언어 변경 완료 및 모든 컴포넌트에 즉시 알림:", newLang);
console.log("✅ 사용자 로케일 변경 성공:", newLang);
} else { } else {
console.error("❌ 사용자 로케일 변경 실패:", response.data.message); console.error("❌ 사용자 로케일 변경 실패:", response.data.message);
} }
} catch (error) { } catch (error) {
console.error("❌ 사용자 로케일 변경 중 오류:", error); console.error("❌ 사용자 로케일 변경 중 오류:", error);
// 오류 시에도 로컬 상태는 변경 // 오류 시에도 클라이언트 상태는 변경
globalUserLang = newLang; notifyLanguageChange(newLang);
setUserLang(newLang);
} }
}; };

View File

@ -68,7 +68,7 @@ export const useProfile = (user: any, refreshUserData: () => Promise<void>, refr
try { try {
const response = await apiCall("GET", "/admin/departments"); const response = await apiCall("GET", "/admin/departments");
if (response.success && response.data) { if (response.success && response.data) {
setDepartments(response.data); setDepartments(response.data as Array<{ deptCode: string; deptName: string }>);
} }
} catch (error) { } catch (error) {
console.error("부서 목록 로드 실패:", error); console.error("부서 목록 로드 실패:", error);
@ -227,17 +227,14 @@ export const useProfile = (user: any, refreshUserData: () => Promise<void>, refr
console.log("프로필 업데이트 응답:", response); console.log("프로필 업데이트 응답:", response);
if (response.result) { if (response.success || (response as any).result) {
// locale이 변경된 경우 전역 변수와 localStorage 업데이트 // locale이 변경된 경우 전역 변수와 localStorage 업데이트
const localeChanged = modalState.formData.locale && modalState.formData.locale !== user.locale; const localeChanged = modalState.formData.locale && modalState.formData.locale !== user.locale;
if (localeChanged) { if (localeChanged) {
if (typeof window !== "undefined") { // 🎯 useMultiLang의 콜백 시스템을 사용하여 모든 컴포넌트에 즉시 알림
// 전역 변수 업데이트 const { notifyLanguageChange } = await import("@/hooks/useMultiLang");
(window as any).__GLOBAL_USER_LANG = modalState.formData.locale; notifyLanguageChange(modalState.formData.locale);
// localStorage 업데이트 console.log("🌍 사용자 locale 업데이트 (콜백 방식):", modalState.formData.locale);
localStorage.setItem("userLocale", modalState.formData.locale);
console.log("🌍 사용자 locale 업데이트:", modalState.formData.locale);
}
} }
// 성공: 사용자 정보 새로고침 // 성공: 사용자 정보 새로고침
@ -256,7 +253,7 @@ export const useProfile = (user: any, refreshUserData: () => Promise<void>, refr
})); }));
showAlert("저장 완료", "프로필이 성공적으로 업데이트되었습니다.", "success"); showAlert("저장 완료", "프로필이 성공적으로 업데이트되었습니다.", "success");
} else { } else {
throw new Error(response.message || "프로필 업데이트 실패"); throw new Error((response as any).message || "프로필 업데이트 실패");
} }
} catch (error) { } catch (error) {
console.error("프로필 저장 실패:", error); console.error("프로필 저장 실패:", error);