- EditModal, InteractiveScreenViewer, SaveModal 컴포넌트에서 리피터 데이터(배열)를 마스터 저장에서 제외하고, 별도로 저장하는 로직을 추가하였습니다. - 리피터 데이터 저장 이벤트를 발생시켜 UnifiedRepeater 컴포넌트가 이를 리스닝하도록 개선하였습니다. - 각 컴포넌트에서 최종 저장 데이터 로그를 업데이트하여, 저장 과정에서의 데이터 흐름을 명확히 하였습니다. 이로 인해 데이터 저장의 효율성과 리피터 관리의 일관성이 향상되었습니다.
210 lines
7.2 KiB
TypeScript
210 lines
7.2 KiB
TypeScript
import { useState, useEffect } from "react";
|
|
import { apiClient } from "@/lib/api/client";
|
|
|
|
// 전역 언어 상태 (다른 컴포넌트에서 접근 가능)
|
|
let globalUserLang = "KR";
|
|
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 } = {}) => {
|
|
const [userLang, setUserLang] = useState<string | null>(null); // null로 시작
|
|
const companyCode = options.companyCode || "*";
|
|
|
|
// 🎯 효율적인 언어 변경 감지 (폴링 대신 콜백 등록)
|
|
useEffect(() => {
|
|
// 언어 변경 콜백 등록
|
|
const handleLanguageChange = (newLang: string) => {
|
|
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(() => {
|
|
// 언어가 설정된 경우에만 콜백 호출
|
|
if (globalChangeLangCallback && userLang) {
|
|
globalChangeLangCallback(userLang);
|
|
}
|
|
}, [userLang]);
|
|
|
|
// 사용자 로케일 조회 (한 번만 실행)
|
|
useEffect(() => {
|
|
// localStorage에서 로케일 확인 (새 창에서도 공유)
|
|
const storedLocale = localStorage.getItem("userLocale");
|
|
const storedLocaleLoaded = localStorage.getItem("userLocaleLoaded");
|
|
|
|
if (storedLocaleLoaded === "true" && storedLocale) {
|
|
setUserLang(storedLocale);
|
|
globalUserLang = storedLocale;
|
|
|
|
// 전역 상태도 동기화
|
|
(window as any).__GLOBAL_USER_LANG = storedLocale;
|
|
(window as any).__GLOBAL_USER_LOCALE_LOADED = true;
|
|
return;
|
|
}
|
|
|
|
// 전역에서 이미 로케일이 로드되었는지 확인
|
|
if ((window as any).__GLOBAL_USER_LOCALE_LOADED) {
|
|
const globalLocale = (window as any).__GLOBAL_USER_LANG;
|
|
console.log("🌐 전역에서 사용자 로케일 사용:", globalLocale);
|
|
setUserLang(globalLocale);
|
|
globalUserLang = globalLocale;
|
|
return;
|
|
}
|
|
|
|
// 이미 로케일이 설정되어 있으면 중복 호출 방지
|
|
if (globalUserLang) {
|
|
setUserLang(globalUserLang);
|
|
return;
|
|
}
|
|
|
|
// 전역 로케일이 아직 로드되지 않았으면 대기
|
|
console.log("⏳ 전역 로케일 로드 대기 중...");
|
|
|
|
// 주기적으로 전역 로케일 확인 (더 빠른 간격으로)
|
|
const checkInterval = setInterval(() => {
|
|
if ((window as any).__GLOBAL_USER_LOCALE_LOADED) {
|
|
const globalLocale = (window as any).__GLOBAL_USER_LANG;
|
|
console.log("🌐 전역에서 사용자 로케일 확인됨:", globalLocale);
|
|
setUserLang(globalLocale);
|
|
globalUserLang = globalLocale;
|
|
clearInterval(checkInterval);
|
|
}
|
|
}, 50); // 50ms로 단축
|
|
|
|
// 3초 후 타임아웃 (더 빠른 타임아웃)
|
|
setTimeout(() => {
|
|
clearInterval(checkInterval);
|
|
if (!userLang) {
|
|
console.warn("⚠️ 전역 로케일 로드 타임아웃, 기본값 사용");
|
|
setUserLang("KR");
|
|
globalUserLang = "KR";
|
|
}
|
|
}, 3000); // 3초로 단축
|
|
|
|
return () => clearInterval(checkInterval);
|
|
}, []);
|
|
|
|
// 다국어 텍스트 가져오기 (배치 조회 방식)
|
|
const getText = async (menuCode: string, langKey: string, fallback?: string): Promise<string> => {
|
|
console.log("🔍 다국어 텍스트 요청 (배치 방식):", { menuCode, langKey, userLang, companyCode });
|
|
|
|
try {
|
|
// 배치 조회 API 사용
|
|
const response = await apiClient.post(
|
|
"/multilang/batch",
|
|
{
|
|
langKeys: [langKey],
|
|
},
|
|
{
|
|
params: {
|
|
companyCode,
|
|
menuCode,
|
|
userLang,
|
|
},
|
|
},
|
|
);
|
|
|
|
console.log("📡 배치 API 응답 상태:", response.status, response.statusText);
|
|
|
|
if (response.data.success && response.data.data && response.data.data[langKey]) {
|
|
// 번역 텍스트를 캐시에 저장
|
|
const cacheKey = `${menuCode}.${langKey}`;
|
|
const currentCache = (window as any).__TRANSLATION_CACHE || {};
|
|
currentCache[cacheKey] = response.data.data[langKey];
|
|
(window as any).__TRANSLATION_CACHE = currentCache;
|
|
|
|
return response.data.data[langKey];
|
|
}
|
|
|
|
// 실패 시 fallback 또는 키 반환
|
|
console.log("🔄 배치 API 성공했지만 데이터 없음, fallback 반환:", fallback || langKey);
|
|
return fallback || langKey;
|
|
} catch (error) {
|
|
console.error("❌ 다국어 텍스트 배치 조회 실패:", error);
|
|
console.log("🔄 에러 시 fallback 반환:", fallback || langKey);
|
|
return fallback || langKey;
|
|
}
|
|
};
|
|
|
|
// 🎯 효율적인 언어 변경 함수 (콜백 방식)
|
|
const changeLang = async (newLang: string) => {
|
|
console.log("🚀 useMultiLang changeLang 호출:", { newLang, userLang });
|
|
|
|
// 같은 언어로 변경하려는 경우 무시
|
|
if (newLang === userLang) {
|
|
console.log("🔄 같은 언어로 변경 시도 무시:", newLang);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
console.log("🔄 언어 변경 시작:", { from: userLang, to: newLang });
|
|
|
|
// 백엔드에 사용자 로케일 설정 요청
|
|
const response = await apiClient.post("/admin/user-locale", {
|
|
locale: newLang,
|
|
});
|
|
|
|
if (response.data.success) {
|
|
// 🎯 핵심: 즉시 모든 컴포넌트에 알림 (폴링 없이!)
|
|
notifyLanguageChange(newLang);
|
|
|
|
console.log("✅ 언어 변경 완료 및 모든 컴포넌트에 즉시 알림:", newLang);
|
|
} else {
|
|
console.error("❌ 사용자 로케일 변경 실패:", response.data.message);
|
|
}
|
|
} catch (error) {
|
|
console.error("❌ 사용자 로케일 변경 중 오류:", error);
|
|
// 오류 시에도 클라이언트 상태는 변경
|
|
notifyLanguageChange(newLang);
|
|
}
|
|
};
|
|
|
|
// 전역 언어 상태 접근자
|
|
const getGlobalUserLang = () => globalUserLang;
|
|
const setGlobalChangeLangCallback = (callback: (lang: string) => void) => {
|
|
globalChangeLangCallback = callback;
|
|
};
|
|
|
|
return {
|
|
userLang,
|
|
getText,
|
|
changeLang,
|
|
companyCode,
|
|
getGlobalUserLang,
|
|
setGlobalChangeLangCallback,
|
|
};
|
|
};
|