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

433 lines
18 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { apiClient } from "../api/client";
// 메뉴 관리 화면 다국어 키 상수
export const MENU_MANAGEMENT_KEYS = {
// 기본 정보
TITLE: "menu.management.title",
DESCRIPTION: "menu.management.description",
MENU_TYPE_TITLE: "menu.type.title",
MENU_TYPE_ADMIN: "menu.type.admin",
MENU_TYPE_USER: "menu.type.user",
ADMIN_MENU: "admin.menu",
USER_MENU: "user.menu",
ADMIN_DESCRIPTION: "admin.description",
USER_DESCRIPTION: "user.description",
LIST_TITLE: "list.title",
LIST_TOTAL: "list.total",
LIST_SEARCH_RESULT: "list.search.result",
// 필터 관련
FILTER_COMPANY: "filter.company",
FILTER_COMPANY_ALL: "filter.company.all",
FILTER_COMPANY_COMMON: "filter.company.common",
FILTER_COMPANY_SEARCH: "filter.company.search",
FILTER_SEARCH: "filter.search",
FILTER_SEARCH_PLACEHOLDER: "filter.search.placeholder",
FILTER_RESET: "filter.reset",
// 버튼 관련
BUTTON_ADD: "button.add",
BUTTON_ADD_TOP_LEVEL: "button.add.top.level",
BUTTON_ADD_SUB: "button.add.sub",
BUTTON_EDIT: "button.edit",
BUTTON_DELETE: "button.delete",
BUTTON_DELETE_SELECTED: "button.delete.selected",
BUTTON_DELETE_SELECTED_COUNT: "button.delete.selected.count",
BUTTON_DELETE_PROCESSING: "button.delete.processing",
BUTTON_CANCEL: "button.cancel",
BUTTON_SAVE: "button.save",
BUTTON_SAVE_PROCESSING: "button.save.processing",
BUTTON_REGISTER: "button.register",
BUTTON_MODIFY: "button.modify",
// 폼 관련
FORM_MENU_TYPE: "form.menu.type",
FORM_MENU_TYPE_ADMIN: "form.menu.type.admin",
FORM_MENU_TYPE_USER: "form.menu.type.user",
FORM_STATUS: "form.status",
FORM_STATUS_ACTIVE: "form.status.active",
FORM_STATUS_INACTIVE: "form.status.inactive",
FORM_COMPANY: "form.company",
FORM_COMPANY_SELECT: "form.company.select",
FORM_COMPANY_COMMON: "form.company.common",
FORM_COMPANY_SUBMENU_NOTE: "form.company.submenu.note",
FORM_MENU_NAME: "form.menu.name",
FORM_MENU_NAME_PLACEHOLDER: "form.menu.name.placeholder",
FORM_MENU_URL: "form.menu.url",
FORM_MENU_URL_PLACEHOLDER: "form.menu.url.placeholder",
FORM_MENU_DESCRIPTION: "form.menu.description",
FORM_MENU_DESCRIPTION_PLACEHOLDER: "form.menu.description.placeholder",
FORM_MENU_SEQUENCE: "form.menu.sequence",
FORM_LANG_KEY: "form.lang.key",
FORM_LANG_KEY_SELECT: "form.lang.key.select",
FORM_LANG_KEY_NONE: "form.lang.key.none",
FORM_LANG_KEY_SEARCH: "form.lang.key.search",
FORM_LANG_KEY_SELECTED: "form.lang.key.selected",
// 모달 관련
MODAL_MENU_REGISTER_TITLE: "modal.menu.register.title",
MODAL_MENU_MODIFY_TITLE: "modal.menu.modify.title",
MODAL_DELETE_TITLE: "modal.delete.title",
MODAL_DELETE_DESCRIPTION: "modal.delete.description",
MODAL_DELETE_BATCH_DESCRIPTION: "modal.delete.batch.description",
// 테이블 헤더 관련
TABLE_HEADER_SELECT: "table.header.select",
TABLE_HEADER_MENU_NAME: "table.header.menu.name",
TABLE_HEADER_MENU_URL: "table.header.menu.url",
TABLE_HEADER_MENU_TYPE: "table.header.menu.type",
TABLE_HEADER_STATUS: "table.header.status",
TABLE_HEADER_COMPANY: "table.header.company",
TABLE_HEADER_SEQUENCE: "table.header.sequence",
TABLE_HEADER_ACTIONS: "table.header.actions",
// 상태 관련
STATUS_ACTIVE: "status.active",
STATUS_INACTIVE: "status.inactive",
STATUS_UNSPECIFIED: "status.unspecified",
// 메시지 관련
MESSAGE_LOADING: "message.loading",
MESSAGE_MENU_DELETE_PROCESSING: "message.menu.delete.processing",
MESSAGE_MENU_SAVE_SUCCESS: "message.menu.save.success",
MESSAGE_MENU_SAVE_FAILED: "message.menu.save.failed",
MESSAGE_MENU_DELETE_SUCCESS: "message.menu.delete.success",
MESSAGE_MENU_DELETE_FAILED: "message.menu.delete.failed",
MESSAGE_MENU_DELETE_BATCH_SUCCESS: "message.menu.delete.batch.success",
MESSAGE_MENU_DELETE_BATCH_PARTIAL: "message.menu.delete.batch.partial",
MESSAGE_MENU_STATUS_TOGGLE_SUCCESS: "message.menu.status.toggle.success",
MESSAGE_MENU_STATUS_TOGGLE_FAILED: "message.menu.status.toggle.failed",
MESSAGE_VALIDATION_MENU_NAME_REQUIRED: "message.validation.menu.name.required",
MESSAGE_VALIDATION_COMPANY_REQUIRED: "message.validation.company.required",
MESSAGE_VALIDATION_SELECT_MENU_DELETE: "message.validation.select.menu.delete",
MESSAGE_ERROR_LOAD_MENU_LIST: "message.error.load.menu.list",
MESSAGE_ERROR_LOAD_MENU_INFO: "message.error.load.menu.info",
MESSAGE_ERROR_LOAD_COMPANY_LIST: "message.error.load.company.list",
MESSAGE_ERROR_LOAD_LANG_KEY_LIST: "message.error.load.lang.key.list",
// 기타 UI 요소
UI_EXPAND: "ui.expand",
UI_COLLAPSE: "ui.collapse",
UI_MENU_COLLAPSE: "ui.menu.collapse",
UI_LANGUAGE: "ui.language",
} as const;
// 다국어 텍스트 캐시 (메모리 기반)
const translationCache: Record<string, Record<string, string>> = {};
// 배치 조회를 위한 키 수집기
const pendingKeys: Set<string> = new Set();
let batchTimeout: NodeJS.Timeout | null = null;
const BATCH_DELAY = 50; // 50ms 지연으로 배치 처리
/**
* 다국어 텍스트 배치 조회
* 여러 키를 한번에 조회하여 API 호출 횟수를 대폭 줄임
*/
async function fetchBatchTranslations(
keys: string[],
companyCode: string = "*",
menuCode: string = "MENU_MANAGEMENT",
userLang: string = "KR",
): Promise<Record<string, string>> {
try {
console.log(`🚀 배치 조회 시작: ${keys.length}개 키`);
const response = await apiClient.post(
"/multilang/batch",
{
langKeys: keys,
},
{
params: {
companyCode,
menuCode,
userLang,
},
},
);
if (response.data.success) {
console.log(`✅ 배치 조회 성공: ${keys.length}개 키`);
return response.data.data || {};
} else {
console.error("❌ 배치 조회 실패:", response.data.message);
return {};
}
} catch (error) {
console.error("❌ 배치 조회 오류:", error);
return {};
}
}
/**
* 개별 다국어 텍스트 조회 (배치 처리)
* 실제로는 배치로 처리되어 API 호출 횟수가 대폭 감소
*/
export async function getMultilangText(
key: string,
companyCode: string = "*",
menuCode: string = "MENU_MANAGEMENT",
userLang: string = "KR",
): Promise<string> {
// 1. 캐시에서 먼저 확인
const cacheKey = `${userLang}_${companyCode}_${menuCode}`;
if (translationCache[cacheKey]?.[key]) {
return translationCache[cacheKey][key];
}
// 2. 기본 텍스트에서 확인
const defaultText = getDefaultText(key);
if (defaultText) {
return defaultText;
}
// 3. 배치 처리에 추가
pendingKeys.add(key);
// 4. 배치 타임아웃 설정
if (batchTimeout) {
clearTimeout(batchTimeout);
}
return new Promise((resolve) => {
batchTimeout = setTimeout(async () => {
try {
const keysToFetch = Array.from(pendingKeys);
pendingKeys.clear();
if (keysToFetch.length > 0) {
const translations = await fetchBatchTranslations(keysToFetch, companyCode, menuCode, userLang);
// 캐시에 저장
if (!translationCache[cacheKey]) {
translationCache[cacheKey] = {};
}
Object.assign(translationCache[cacheKey], translations);
// 요청된 키에 대한 번역 반환
if (translations[key]) {
resolve(translations[key]);
} else {
resolve(defaultText || key);
}
} else {
resolve(defaultText || key);
}
} catch (error) {
console.error("❌ 배치 처리 오류:", error);
resolve(defaultText || key);
}
}, BATCH_DELAY);
});
}
/**
* 동기적 다국어 텍스트 조회 (캐시에서만)
* UI 렌더링 시 즉시 사용
*/
export function getMultilangTextSync(
key: string,
companyCode: string = "*",
menuCode: string = "MENU_MANAGEMENT",
userLang: string = "KR",
): string {
// 1. 캐시에서 확인
const cacheKey = `${userLang}_${companyCode}_${menuCode}`;
if (translationCache[cacheKey]?.[key]) {
return translationCache[cacheKey][key];
}
// 2. 기본 텍스트에서 확인
const defaultText = getDefaultText(key);
if (defaultText) {
return defaultText;
}
// 3. 캐시에 없으면 비동기적으로 로드 (백그라운드)
if (typeof window !== "undefined") {
getMultilangText(key, companyCode, menuCode, userLang).then((text) => {
// 페이지 리렌더링을 위해 이벤트 발생
window.dispatchEvent(
new CustomEvent("translation-loaded", {
detail: { key, text, userLang },
}),
);
});
}
return defaultText || key;
}
/**
* 메뉴 관리 관련 다국어 텍스트 조회 (배치 처리)
*/
export async function getMenuText(key: string, userLang: string = "KR"): Promise<string> {
return getMultilangText(key, "*", "MENU_MANAGEMENT", userLang);
}
/**
* 메뉴 관리 관련 다국어 텍스트 동기 조회
*/
export function getMenuTextSync(key: string, userLang: string = "KR"): string {
return getMultilangTextSync(key, "*", "MENU_MANAGEMENT", userLang);
}
/**
* 기본 텍스트 반환 (한국어)
*/
function getDefaultText(key: string): string {
const defaultTexts: Record<string, string> = {
[MENU_MANAGEMENT_KEYS.TITLE]: "메뉴 관리",
[MENU_MANAGEMENT_KEYS.DESCRIPTION]: "시스템의 메뉴 구조와 권한을 관리합니다.",
[MENU_MANAGEMENT_KEYS.MENU_TYPE_TITLE]: "메뉴 타입",
[MENU_MANAGEMENT_KEYS.MENU_TYPE_ADMIN]: "관리자",
[MENU_MANAGEMENT_KEYS.MENU_TYPE_USER]: "사용자",
[MENU_MANAGEMENT_KEYS.ADMIN_MENU]: "관리자 메뉴",
[MENU_MANAGEMENT_KEYS.USER_MENU]: "사용자 메뉴",
[MENU_MANAGEMENT_KEYS.ADMIN_DESCRIPTION]: "시스템 관리 및 설정 메뉴",
[MENU_MANAGEMENT_KEYS.USER_DESCRIPTION]: "일반 사용자 업무 메뉴",
[MENU_MANAGEMENT_KEYS.LIST_TITLE]: "메뉴 목록",
[MENU_MANAGEMENT_KEYS.LIST_TOTAL]: "전체",
[MENU_MANAGEMENT_KEYS.LIST_SEARCH_RESULT]: "검색 결과",
[MENU_MANAGEMENT_KEYS.FILTER_COMPANY]: "회사 필터",
[MENU_MANAGEMENT_KEYS.FILTER_COMPANY_ALL]: "전체",
[MENU_MANAGEMENT_KEYS.FILTER_COMPANY_COMMON]: "공통",
[MENU_MANAGEMENT_KEYS.FILTER_COMPANY_SEARCH]: "회사 검색...",
[MENU_MANAGEMENT_KEYS.FILTER_SEARCH]: "검색",
[MENU_MANAGEMENT_KEYS.FILTER_SEARCH_PLACEHOLDER]: "메뉴명 검색...",
[MENU_MANAGEMENT_KEYS.FILTER_RESET]: "초기화",
[MENU_MANAGEMENT_KEYS.BUTTON_ADD]: "추가",
[MENU_MANAGEMENT_KEYS.BUTTON_ADD_TOP_LEVEL]: "최상위 메뉴 추가",
[MENU_MANAGEMENT_KEYS.BUTTON_ADD_SUB]: "하위",
[MENU_MANAGEMENT_KEYS.BUTTON_EDIT]: "수정",
[MENU_MANAGEMENT_KEYS.BUTTON_DELETE]: "삭제",
[MENU_MANAGEMENT_KEYS.BUTTON_DELETE_SELECTED]: "선택 삭제",
[MENU_MANAGEMENT_KEYS.BUTTON_DELETE_SELECTED_COUNT]: "선택 삭제 ({count})",
[MENU_MANAGEMENT_KEYS.BUTTON_DELETE_PROCESSING]: "삭제 중...",
[MENU_MANAGEMENT_KEYS.BUTTON_CANCEL]: "취소",
[MENU_MANAGEMENT_KEYS.BUTTON_SAVE]: "저장",
[MENU_MANAGEMENT_KEYS.BUTTON_SAVE_PROCESSING]: "저장 중...",
[MENU_MANAGEMENT_KEYS.BUTTON_REGISTER]: "등록",
[MENU_MANAGEMENT_KEYS.BUTTON_MODIFY]: "수정",
[MENU_MANAGEMENT_KEYS.FORM_MENU_TYPE]: "메뉴 타입",
[MENU_MANAGEMENT_KEYS.FORM_MENU_TYPE_ADMIN]: "관리자",
[MENU_MANAGEMENT_KEYS.FORM_MENU_TYPE_USER]: "사용자",
[MENU_MANAGEMENT_KEYS.FORM_STATUS]: "상태",
[MENU_MANAGEMENT_KEYS.FORM_STATUS_ACTIVE]: "활성화",
[MENU_MANAGEMENT_KEYS.FORM_STATUS_INACTIVE]: "비활성화",
[MENU_MANAGEMENT_KEYS.FORM_COMPANY]: "회사",
[MENU_MANAGEMENT_KEYS.FORM_COMPANY_SELECT]: "회사를 선택하세요",
[MENU_MANAGEMENT_KEYS.FORM_COMPANY_COMMON]: "공통",
[MENU_MANAGEMENT_KEYS.FORM_COMPANY_SUBMENU_NOTE]: "하위 메뉴는 상위 메뉴와 동일한 회사를 가져야 합니다.",
[MENU_MANAGEMENT_KEYS.FORM_MENU_NAME]: "메뉴명",
[MENU_MANAGEMENT_KEYS.FORM_MENU_NAME_PLACEHOLDER]: "메뉴명을 입력하세요",
[MENU_MANAGEMENT_KEYS.FORM_MENU_URL]: "URL",
[MENU_MANAGEMENT_KEYS.FORM_MENU_URL_PLACEHOLDER]: "메뉴 URL을 입력하세요",
[MENU_MANAGEMENT_KEYS.FORM_MENU_DESCRIPTION]: "설명",
[MENU_MANAGEMENT_KEYS.FORM_MENU_DESCRIPTION_PLACEHOLDER]: "메뉴 설명을 입력하세요",
[MENU_MANAGEMENT_KEYS.FORM_MENU_SEQUENCE]: "순서",
[MENU_MANAGEMENT_KEYS.FORM_LANG_KEY]: "다국어 키",
[MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_SELECT]: "다국어 키를 선택하세요",
[MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_NONE]: "다국어 키 없음",
[MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_SEARCH]: "다국어 키 검색...",
[MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_SELECTED]: "선택된 키: {key} - {description}",
[MENU_MANAGEMENT_KEYS.MODAL_MENU_REGISTER_TITLE]: "메뉴 등록",
[MENU_MANAGEMENT_KEYS.MODAL_MENU_MODIFY_TITLE]: "메뉴 수정",
[MENU_MANAGEMENT_KEYS.MODAL_DELETE_TITLE]: "메뉴 삭제",
[MENU_MANAGEMENT_KEYS.MODAL_DELETE_DESCRIPTION]: "해당 메뉴를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.",
[MENU_MANAGEMENT_KEYS.MODAL_DELETE_BATCH_DESCRIPTION]:
"선택된 {count}개의 메뉴를 영구적으로 삭제하시겠습니까?\n\n⚠ 주의: 상위 메뉴를 삭제하면 하위 메뉴들도 함께 삭제됩니다.\n이 작업은 되돌릴 수 없습니다.",
[MENU_MANAGEMENT_KEYS.TABLE_HEADER_SELECT]: "선택",
[MENU_MANAGEMENT_KEYS.TABLE_HEADER_MENU_NAME]: "메뉴명",
[MENU_MANAGEMENT_KEYS.TABLE_HEADER_MENU_URL]: "URL",
[MENU_MANAGEMENT_KEYS.TABLE_HEADER_MENU_TYPE]: "메뉴 타입",
[MENU_MANAGEMENT_KEYS.TABLE_HEADER_STATUS]: "상태",
[MENU_MANAGEMENT_KEYS.TABLE_HEADER_COMPANY]: "회사",
[MENU_MANAGEMENT_KEYS.TABLE_HEADER_SEQUENCE]: "순서",
[MENU_MANAGEMENT_KEYS.TABLE_HEADER_ACTIONS]: "작업",
[MENU_MANAGEMENT_KEYS.STATUS_ACTIVE]: "활성화",
[MENU_MANAGEMENT_KEYS.STATUS_INACTIVE]: "비활성화",
[MENU_MANAGEMENT_KEYS.STATUS_UNSPECIFIED]: "미지정",
[MENU_MANAGEMENT_KEYS.MESSAGE_LOADING]: "로딩 중...",
[MENU_MANAGEMENT_KEYS.MESSAGE_MENU_DELETE_PROCESSING]: "메뉴를 삭제하는 중...",
[MENU_MANAGEMENT_KEYS.MESSAGE_MENU_SAVE_SUCCESS]: "메뉴가 성공적으로 저장되었습니다.",
[MENU_MANAGEMENT_KEYS.MESSAGE_MENU_SAVE_FAILED]: "메뉴 저장에 실패했습니다.",
[MENU_MANAGEMENT_KEYS.MESSAGE_MENU_DELETE_SUCCESS]: "메뉴가 성공적으로 삭제되었습니다.",
[MENU_MANAGEMENT_KEYS.MESSAGE_MENU_DELETE_FAILED]: "메뉴 삭제에 실패했습니다.",
[MENU_MANAGEMENT_KEYS.MESSAGE_MENU_DELETE_BATCH_SUCCESS]: "{count}개의 메뉴가 성공적으로 삭제되었습니다.",
[MENU_MANAGEMENT_KEYS.MESSAGE_MENU_DELETE_BATCH_PARTIAL]: "{success}개 삭제됨, {failed}개 실패",
[MENU_MANAGEMENT_KEYS.MESSAGE_MENU_STATUS_TOGGLE_SUCCESS]: "메뉴 상태가 변경되었습니다.",
[MENU_MANAGEMENT_KEYS.MESSAGE_MENU_STATUS_TOGGLE_FAILED]: "메뉴 상태 변경에 실패했습니다.",
[MENU_MANAGEMENT_KEYS.MESSAGE_VALIDATION_MENU_NAME_REQUIRED]: "메뉴명을 입력하세요.",
[MENU_MANAGEMENT_KEYS.MESSAGE_VALIDATION_COMPANY_REQUIRED]: "회사를 선택하세요.",
[MENU_MANAGEMENT_KEYS.MESSAGE_VALIDATION_SELECT_MENU_DELETE]: "삭제할 메뉴를 선택하세요.",
[MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_MENU_LIST]: "메뉴 목록을 불러오는데 실패했습니다.",
[MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_MENU_INFO]: "메뉴 정보를 불러오는데 실패했습니다.",
[MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_COMPANY_LIST]: "회사 목록을 불러오는데 실패했습니다.",
[MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_LANG_KEY_LIST]: "다국어 키 목록을 불러오는데 실패했습니다.",
[MENU_MANAGEMENT_KEYS.UI_EXPAND]: "펼치기",
[MENU_MANAGEMENT_KEYS.UI_COLLAPSE]: "접기",
[MENU_MANAGEMENT_KEYS.UI_MENU_COLLAPSE]: "메뉴 접기",
[MENU_MANAGEMENT_KEYS.UI_LANGUAGE]: "언어",
// 추가 매핑: key 문자열 자체도 한글로 매핑
"menu.type.title": "메뉴 타입",
"menu.management.admin": "관리자",
"menu.management.admin.description": "시스템 관리 및 설정 메뉴",
"menu.management.user": "사용자",
"menu.management.user.description": "일반 사용자 업무 메뉴",
"menu.list.title": "메뉴 목록",
"filter.company.all": "전체",
"filter.search.placeholder": "메뉴명 검색...",
"filter.reset": "초기화",
"button.add.top.level": "최상위 메뉴 추가",
"button.delete.selected": "선택 삭제",
"table.header.menu.name": "메뉴명",
"table.header.sequence": "순서",
"table.header.company": "회사",
"table.header.menu.url": "URL",
"table.header.status": "상태",
"table.header.actions": "작업",
};
return defaultTexts[key] || "";
}
/**
* 번역 캐시 설정 함수
*/
export const setTranslationCache = (lang: string, translations: Record<string, string>) => {
translationCache[lang] = translations;
};
/**
* 번역 캐시 가져오기 함수
*/
export const getTranslationCache = (lang: string): Record<string, string> => {
return translationCache[lang] || {};
};
/**
* 메뉴 관리 다국어 텍스트 훅 (기존 코드와 호환)
*/
export const useMenuManagementText = () => {
const getMenuText = async (key: string, params?: Record<string, string | number>): Promise<string> => {
let text = await getMultilangText(key, "*", "MENU_MANAGEMENT", "KR");
// 파라미터 치환
if (params) {
Object.entries(params).forEach(([paramKey, paramValue]) => {
text = text.replace(new RegExp(`\\{${paramKey}\\}`, "g"), paramValue.toString());
});
}
return text;
};
return {
getMenuText,
keys: MENU_MANAGEMENT_KEYS,
};
};