Merge pull request 'dev' (#47) from dev into main
Reviewed-on: http://39.117.244.52:3000/kjs/ERP-node/pulls/47
This commit is contained in:
commit
cc4398afbf
|
|
@ -1,202 +1,496 @@
|
|||
import { Response } from "express";
|
||||
import { AuthenticatedRequest } from "../types/auth";
|
||||
import { Request, Response } from "express";
|
||||
import { logger } from "../utils/logger";
|
||||
import prisma from "../config/database";
|
||||
|
||||
// 메모리 캐시 (개발 환경용, 운영에서는 Redis 사용 권장)
|
||||
const translationCache = new Map<string, any>();
|
||||
const CACHE_TTL = 5 * 60 * 1000; // 5분
|
||||
|
||||
interface CacheEntry {
|
||||
data: any;
|
||||
timestamp: number;
|
||||
}
|
||||
import { AuthenticatedRequest } from "../types/auth";
|
||||
import { MultiLangService } from "../services/multilangService";
|
||||
import {
|
||||
CreateLanguageRequest,
|
||||
UpdateLanguageRequest,
|
||||
CreateLangKeyRequest,
|
||||
UpdateLangKeyRequest,
|
||||
SaveLangTextsRequest,
|
||||
GetUserTextParams,
|
||||
BatchTranslationRequest,
|
||||
ApiResponse,
|
||||
} from "../types/multilang";
|
||||
|
||||
/**
|
||||
* GET /api/multilang/batch
|
||||
* 다국어 텍스트 배치 조회 API - 여러 키를 한번에 조회
|
||||
* GET /api/multilang/languages
|
||||
* 언어 목록 조회 API
|
||||
*/
|
||||
export const getBatchTranslations = async (
|
||||
export const getLanguages = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { companyCode, menuCode, userLang } = req.query;
|
||||
const { langKeys } = req.body; // 배열로 여러 키 전달
|
||||
logger.info("언어 목록 조회 요청", { user: req.user });
|
||||
|
||||
logger.info("다국어 텍스트 배치 조회 요청", {
|
||||
companyCode,
|
||||
menuCode,
|
||||
userLang,
|
||||
keyCount: langKeys?.length || 0,
|
||||
const multiLangService = new MultiLangService();
|
||||
const languages = await multiLangService.getLanguages();
|
||||
|
||||
const response: ApiResponse<any[]> = {
|
||||
success: true,
|
||||
message: "언어 목록 조회 성공",
|
||||
data: languages,
|
||||
};
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
logger.error("언어 목록 조회 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "언어 목록 조회 중 오류가 발생했습니다.",
|
||||
error: {
|
||||
code: "LANGUAGE_LIST_ERROR",
|
||||
details: error instanceof Error ? error.message : "Unknown error",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* POST /api/multilang/languages
|
||||
* 언어 생성 API
|
||||
*/
|
||||
export const createLanguage = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const languageData: CreateLanguageRequest = req.body;
|
||||
logger.info("언어 생성 요청", { languageData, user: req.user });
|
||||
|
||||
// 필수 입력값 검증
|
||||
if (
|
||||
!languageData.langCode ||
|
||||
!languageData.langName ||
|
||||
!languageData.langNative
|
||||
) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "언어 코드, 언어명, 원어명은 필수입니다.",
|
||||
error: {
|
||||
code: "MISSING_REQUIRED_FIELDS",
|
||||
details: "langCode, langName, langNative are required",
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const multiLangService = new MultiLangService();
|
||||
const createdLanguage = await multiLangService.createLanguage({
|
||||
...languageData,
|
||||
createdBy: req.user?.userId || "system",
|
||||
updatedBy: req.user?.userId || "system",
|
||||
});
|
||||
|
||||
const response: ApiResponse<any> = {
|
||||
success: true,
|
||||
message: "언어가 성공적으로 생성되었습니다.",
|
||||
data: createdLanguage,
|
||||
};
|
||||
|
||||
res.status(201).json(response);
|
||||
} catch (error) {
|
||||
logger.error("언어 생성 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "언어 생성 중 오류가 발생했습니다.",
|
||||
error: {
|
||||
code: "LANGUAGE_CREATE_ERROR",
|
||||
details: error instanceof Error ? error.message : "Unknown error",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* PUT /api/multilang/languages/:langCode
|
||||
* 언어 수정 API
|
||||
*/
|
||||
export const updateLanguage = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { langCode } = req.params;
|
||||
const languageData: UpdateLanguageRequest = req.body;
|
||||
|
||||
logger.info("언어 수정 요청", { langCode, languageData, user: req.user });
|
||||
|
||||
const multiLangService = new MultiLangService();
|
||||
const updatedLanguage = await multiLangService.updateLanguage(langCode, {
|
||||
...languageData,
|
||||
updatedBy: req.user?.userId || "system",
|
||||
});
|
||||
|
||||
const response: ApiResponse<any> = {
|
||||
success: true,
|
||||
message: "언어가 성공적으로 수정되었습니다.",
|
||||
data: updatedLanguage,
|
||||
};
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
logger.error("언어 수정 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "언어 수정 중 오류가 발생했습니다.",
|
||||
error: {
|
||||
code: "LANGUAGE_UPDATE_ERROR",
|
||||
details: error instanceof Error ? error.message : "Unknown error",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* PUT /api/multilang/languages/:langCode/toggle
|
||||
* 언어 상태 토글 API
|
||||
*/
|
||||
export const toggleLanguage = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { langCode } = req.params;
|
||||
logger.info("언어 상태 토글 요청", { langCode, user: req.user });
|
||||
|
||||
const multiLangService = new MultiLangService();
|
||||
const result = await multiLangService.toggleLanguage(langCode);
|
||||
|
||||
const response: ApiResponse<string> = {
|
||||
success: true,
|
||||
message: `언어가 ${result}되었습니다.`,
|
||||
data: result,
|
||||
};
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
logger.error("언어 상태 토글 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "언어 상태 변경 중 오류가 발생했습니다.",
|
||||
error: {
|
||||
code: "LANGUAGE_TOGGLE_ERROR",
|
||||
details: error instanceof Error ? error.message : "Unknown error",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* GET /api/multilang/keys
|
||||
* 다국어 키 목록 조회 API
|
||||
*/
|
||||
export const getLangKeys = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { companyCode, menuCode, keyType, searchText } = req.query;
|
||||
logger.info("다국어 키 목록 조회 요청", {
|
||||
query: req.query,
|
||||
user: req.user,
|
||||
});
|
||||
|
||||
if (!langKeys || !Array.isArray(langKeys) || langKeys.length === 0) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "langKeys 배열이 필요합니다.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 캐시 키 생성
|
||||
const cacheKey = `${companyCode}_${userLang}_${langKeys.sort().join("_")}`;
|
||||
|
||||
// 캐시 확인
|
||||
const cached = translationCache.get(cacheKey);
|
||||
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
||||
logger.info("캐시된 번역 데이터 사용", {
|
||||
cacheKey,
|
||||
keyCount: langKeys.length,
|
||||
});
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: cached.data,
|
||||
message: "캐시된 다국어 텍스트 조회 성공",
|
||||
fromCache: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 모든 키에 대한 마스터 정보를 한번에 조회
|
||||
logger.info("다국어 키 마스터 배치 조회 시작", {
|
||||
keyCount: langKeys.length,
|
||||
const multiLangService = new MultiLangService();
|
||||
const langKeys = await multiLangService.getLangKeys({
|
||||
companyCode: companyCode as string,
|
||||
menuCode: menuCode as string,
|
||||
keyType: keyType as string,
|
||||
searchText: searchText as string,
|
||||
});
|
||||
|
||||
const langKeyMasters = await prisma.$queryRaw<any[]>`
|
||||
SELECT key_id, lang_key, company_code
|
||||
FROM multi_lang_key_master
|
||||
WHERE lang_key = ANY(${langKeys}::varchar[])
|
||||
AND (company_code = ${companyCode}::varchar OR company_code = '*')
|
||||
ORDER BY
|
||||
CASE WHEN company_code = ${companyCode}::varchar THEN 1 ELSE 2 END,
|
||||
lang_key,
|
||||
company_code
|
||||
`;
|
||||
|
||||
logger.info("다국어 키 마스터 배치 조회 결과", {
|
||||
requestedKeys: langKeys.length,
|
||||
foundKeys: langKeyMasters.length,
|
||||
});
|
||||
|
||||
if (langKeyMasters.length === 0) {
|
||||
// 마스터 데이터가 없으면 기본값 반환
|
||||
const defaultTranslations = getDefaultTranslations(
|
||||
langKeys,
|
||||
userLang as string
|
||||
);
|
||||
|
||||
// 캐시에 저장
|
||||
translationCache.set(cacheKey, {
|
||||
data: defaultTranslations,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: defaultTranslations,
|
||||
message: "기본값으로 다국어 텍스트 조회 성공",
|
||||
fromCache: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 모든 key_id를 추출
|
||||
const keyIds = langKeyMasters.map((master: any) => master.key_id);
|
||||
|
||||
// 3. 요청된 언어와 한국어 번역을 한번에 조회
|
||||
const translations = await prisma.$queryRaw<any[]>`
|
||||
SELECT
|
||||
mlt.key_id,
|
||||
mlt.lang_code,
|
||||
mlt.lang_text,
|
||||
mlkm.lang_key
|
||||
FROM multi_lang_text mlt
|
||||
JOIN multi_lang_key_master mlkm ON mlt.key_id = mlkm.key_id
|
||||
WHERE mlt.key_id = ANY(${keyIds}::numeric[])
|
||||
AND mlt.lang_code IN (${userLang}::varchar, 'KR')
|
||||
ORDER BY
|
||||
mlt.key_id,
|
||||
CASE WHEN mlt.lang_code = ${userLang}::varchar THEN 1 ELSE 2 END
|
||||
`;
|
||||
|
||||
logger.info("번역 텍스트 배치 조회 결과", {
|
||||
keyIds: keyIds.length,
|
||||
translations: translations.length,
|
||||
});
|
||||
|
||||
// 4. 결과를 키별로 정리
|
||||
const result: Record<string, string> = {};
|
||||
|
||||
for (const langKey of langKeys) {
|
||||
const master = langKeyMasters.find((m) => m.lang_key === langKey);
|
||||
|
||||
if (master) {
|
||||
const keyId = master.key_id;
|
||||
|
||||
// 요청된 언어 번역 찾기
|
||||
let translation = translations.find(
|
||||
(t) => t.key_id === keyId && t.lang_code === userLang
|
||||
);
|
||||
|
||||
// 요청된 언어가 없으면 한국어 번역 찾기
|
||||
if (!translation) {
|
||||
translation = translations.find(
|
||||
(t) => t.key_id === keyId && t.lang_code === "KR"
|
||||
);
|
||||
}
|
||||
|
||||
// 번역이 있으면 사용, 없으면 기본값
|
||||
if (translation) {
|
||||
result[langKey] = translation.lang_text;
|
||||
} else {
|
||||
result[langKey] = getDefaultTranslation(langKey, userLang as string);
|
||||
}
|
||||
} else {
|
||||
// 마스터 데이터가 없으면 기본값
|
||||
result[langKey] = getDefaultTranslation(langKey, userLang as string);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 캐시에 저장
|
||||
translationCache.set(cacheKey, {
|
||||
data: result,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
logger.info("다국어 텍스트 배치 조회 완료", {
|
||||
requestedKeys: langKeys.length,
|
||||
resultKeys: Object.keys(result).length,
|
||||
cacheKey,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
const response: ApiResponse<any[]> = {
|
||||
success: true,
|
||||
data: result,
|
||||
message: "다국어 텍스트 배치 조회 성공",
|
||||
fromCache: false,
|
||||
});
|
||||
message: "다국어 키 목록 조회 성공",
|
||||
data: langKeys,
|
||||
};
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
logger.error("다국어 텍스트 배치 조회 실패", { error });
|
||||
logger.error("다국어 키 목록 조회 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "다국어 텍스트 배치 조회 중 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
message: "다국어 키 목록 조회 중 오류가 발생했습니다.",
|
||||
error: {
|
||||
code: "LANG_KEYS_LIST_ERROR",
|
||||
details: error instanceof Error ? error.message : "Unknown error",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* GET /api/multilang/keys/:keyId/texts
|
||||
* 특정 키의 다국어 텍스트 조회 API
|
||||
*/
|
||||
export const getLangTexts = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { keyId } = req.params;
|
||||
logger.info("다국어 텍스트 조회 요청", { keyId, user: req.user });
|
||||
|
||||
const multiLangService = new MultiLangService();
|
||||
const langTexts = await multiLangService.getLangTexts(parseInt(keyId));
|
||||
|
||||
const response: ApiResponse<any[]> = {
|
||||
success: true,
|
||||
message: "다국어 텍스트 조회 성공",
|
||||
data: langTexts,
|
||||
};
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
logger.error("다국어 텍스트 조회 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "다국어 텍스트 조회 중 오류가 발생했습니다.",
|
||||
error: {
|
||||
code: "LANG_TEXTS_LIST_ERROR",
|
||||
details: error instanceof Error ? error.message : "Unknown error",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* POST /api/multilang/keys
|
||||
* 다국어 키 생성 API
|
||||
*/
|
||||
export const createLangKey = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const keyData: CreateLangKeyRequest = req.body;
|
||||
logger.info("다국어 키 생성 요청", { keyData, user: req.user });
|
||||
|
||||
// 필수 입력값 검증
|
||||
if (!keyData.companyCode || !keyData.langKey) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "회사 코드와 언어 키는 필수입니다.",
|
||||
error: {
|
||||
code: "MISSING_REQUIRED_FIELDS",
|
||||
details: "companyCode and langKey are required",
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const multiLangService = new MultiLangService();
|
||||
const keyId = await multiLangService.createLangKey({
|
||||
...keyData,
|
||||
createdBy: req.user?.userId || "system",
|
||||
updatedBy: req.user?.userId || "system",
|
||||
});
|
||||
|
||||
const response: ApiResponse<number> = {
|
||||
success: true,
|
||||
message: "다국어 키가 성공적으로 생성되었습니다.",
|
||||
data: keyId,
|
||||
};
|
||||
|
||||
res.status(201).json(response);
|
||||
} catch (error) {
|
||||
logger.error("다국어 키 생성 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "다국어 키 생성 중 오류가 발생했습니다.",
|
||||
error: {
|
||||
code: "LANG_KEY_CREATE_ERROR",
|
||||
details: error instanceof Error ? error.message : "Unknown error",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* PUT /api/multilang/keys/:keyId
|
||||
* 다국어 키 수정 API
|
||||
*/
|
||||
export const updateLangKey = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { keyId } = req.params;
|
||||
const keyData: UpdateLangKeyRequest = req.body;
|
||||
|
||||
logger.info("다국어 키 수정 요청", { keyId, keyData, user: req.user });
|
||||
|
||||
const multiLangService = new MultiLangService();
|
||||
await multiLangService.updateLangKey(parseInt(keyId), {
|
||||
...keyData,
|
||||
updatedBy: req.user?.userId || "system",
|
||||
});
|
||||
|
||||
const response: ApiResponse<string> = {
|
||||
success: true,
|
||||
message: "다국어 키가 성공적으로 수정되었습니다.",
|
||||
data: "수정 완료",
|
||||
};
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
logger.error("다국어 키 수정 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "다국어 키 수정 중 오류가 발생했습니다.",
|
||||
error: {
|
||||
code: "LANG_KEY_UPDATE_ERROR",
|
||||
details: error instanceof Error ? error.message : "Unknown error",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* DELETE /api/multilang/keys/:keyId
|
||||
* 다국어 키 삭제 API
|
||||
*/
|
||||
export const deleteLangKey = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { keyId } = req.params;
|
||||
logger.info("다국어 키 삭제 요청", { keyId, user: req.user });
|
||||
|
||||
const multiLangService = new MultiLangService();
|
||||
await multiLangService.deleteLangKey(parseInt(keyId));
|
||||
|
||||
const response: ApiResponse<string> = {
|
||||
success: true,
|
||||
message: "다국어 키가 성공적으로 삭제되었습니다.",
|
||||
data: "삭제 완료",
|
||||
};
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
logger.error("다국어 키 삭제 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "다국어 키 삭제 중 오류가 발생했습니다.",
|
||||
error: {
|
||||
code: "LANG_KEY_DELETE_ERROR",
|
||||
details: error instanceof Error ? error.message : "Unknown error",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* PUT /api/multilang/keys/:keyId/toggle
|
||||
* 다국어 키 상태 토글 API
|
||||
*/
|
||||
export const toggleLangKey = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { keyId } = req.params;
|
||||
logger.info("다국어 키 상태 토글 요청", { keyId, user: req.user });
|
||||
|
||||
const multiLangService = new MultiLangService();
|
||||
const result = await multiLangService.toggleLangKey(parseInt(keyId));
|
||||
|
||||
const response: ApiResponse<string> = {
|
||||
success: true,
|
||||
message: `다국어 키가 ${result}되었습니다.`,
|
||||
data: result,
|
||||
};
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
logger.error("다국어 키 상태 토글 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "다국어 키 상태 변경 중 오류가 발생했습니다.",
|
||||
error: {
|
||||
code: "LANG_KEY_TOGGLE_ERROR",
|
||||
details: error instanceof Error ? error.message : "Unknown error",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* POST /api/multilang/keys/:keyId/texts
|
||||
* 다국어 텍스트 저장/수정 API
|
||||
*/
|
||||
export const saveLangTexts = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { keyId } = req.params;
|
||||
const textData: SaveLangTextsRequest = req.body;
|
||||
|
||||
logger.info("다국어 텍스트 저장 요청", { keyId, textData, user: req.user });
|
||||
|
||||
// 필수 입력값 검증
|
||||
if (
|
||||
!textData.texts ||
|
||||
!Array.isArray(textData.texts) ||
|
||||
textData.texts.length === 0
|
||||
) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "텍스트 데이터는 필수입니다.",
|
||||
error: {
|
||||
code: "MISSING_REQUIRED_FIELDS",
|
||||
details: "texts array is required",
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const multiLangService = new MultiLangService();
|
||||
await multiLangService.saveLangTexts(parseInt(keyId), {
|
||||
texts: textData.texts.map((text) => ({
|
||||
...text,
|
||||
createdBy: req.user?.userId || "system",
|
||||
updatedBy: req.user?.userId || "system",
|
||||
})),
|
||||
});
|
||||
|
||||
const response: ApiResponse<string> = {
|
||||
success: true,
|
||||
message: "다국어 텍스트가 성공적으로 저장되었습니다.",
|
||||
data: "저장 완료",
|
||||
};
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
logger.error("다국어 텍스트 저장 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "다국어 텍스트 저장 중 오류가 발생했습니다.",
|
||||
error: {
|
||||
code: "LANG_TEXTS_SAVE_ERROR",
|
||||
details: error instanceof Error ? error.message : "Unknown error",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* GET /api/multilang/user-text/:companyCode/:menuCode/:langKey
|
||||
* 단일 다국어 텍스트 조회 API (하위 호환성 유지)
|
||||
* 사용자별 다국어 텍스트 조회 API
|
||||
*/
|
||||
export const getUserText = async (req: AuthenticatedRequest, res: Response) => {
|
||||
export const getUserText = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { companyCode, menuCode, langKey } = req.params;
|
||||
const { userLang } = req.query;
|
||||
|
||||
logger.info("단일 다국어 텍스트 조회 요청", {
|
||||
logger.info("사용자별 다국어 텍스트 조회 요청", {
|
||||
companyCode,
|
||||
menuCode,
|
||||
langKey,
|
||||
|
|
@ -204,122 +498,215 @@ export const getUserText = async (req: AuthenticatedRequest, res: Response) => {
|
|||
user: req.user,
|
||||
});
|
||||
|
||||
// 배치 API를 사용하여 단일 키 조회
|
||||
const batchResult = await getBatchTranslations(
|
||||
{
|
||||
...req,
|
||||
body: { langKeys: [langKey] },
|
||||
query: { companyCode, menuCode, userLang },
|
||||
} as any,
|
||||
res
|
||||
);
|
||||
if (!userLang) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "사용자 언어는 필수입니다.",
|
||||
error: {
|
||||
code: "MISSING_USER_LANG",
|
||||
details: "userLang query parameter is required",
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 배치 API에서 이미 응답을 보냈으므로 여기서는 아무것도 하지 않음
|
||||
return;
|
||||
const multiLangService = new MultiLangService();
|
||||
const langText = await multiLangService.getUserText({
|
||||
companyCode,
|
||||
menuCode,
|
||||
langKey,
|
||||
userLang: userLang as string,
|
||||
});
|
||||
|
||||
const response: ApiResponse<string> = {
|
||||
success: true,
|
||||
message: "사용자별 다국어 텍스트 조회 성공",
|
||||
data: langText,
|
||||
};
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
logger.error("단일 다국어 텍스트 조회 실패", { error });
|
||||
logger.error("사용자별 다국어 텍스트 조회 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "다국어 텍스트 조회 중 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
message: "사용자별 다국어 텍스트 조회 중 오류가 발생했습니다.",
|
||||
error: {
|
||||
code: "USER_TEXT_ERROR",
|
||||
details: error instanceof Error ? error.message : "Unknown error",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 기본 번역 텍스트 반환 (개별 키)
|
||||
* GET /api/multilang/text/:companyCode/:langKey/:langCode
|
||||
* 특정 키의 다국어 텍스트 조회 API
|
||||
*/
|
||||
function getDefaultTranslation(langKey: string, userLang: string): string {
|
||||
const defaultKoreanTexts: Record<string, string> = {
|
||||
"button.add": "추가",
|
||||
"button.add.top.level": "최상위 메뉴 추가",
|
||||
"button.add.sub": "하위 메뉴 추가",
|
||||
"button.edit": "수정",
|
||||
"button.delete": "삭제",
|
||||
"button.cancel": "취소",
|
||||
"button.save": "저장",
|
||||
"button.register": "등록",
|
||||
"form.menu.name": "메뉴명",
|
||||
"form.menu.url": "URL",
|
||||
"form.menu.description": "설명",
|
||||
"form.menu.type": "메뉴 타입",
|
||||
"form.status": "상태",
|
||||
"form.company": "회사",
|
||||
"table.header.menu.name": "메뉴명",
|
||||
"table.header.menu.url": "URL",
|
||||
"table.header.status": "상태",
|
||||
"table.header.company": "회사",
|
||||
"table.header.actions": "작업",
|
||||
"filter.company": "회사",
|
||||
"filter.search": "검색",
|
||||
"filter.reset": "초기화",
|
||||
"menu.type.title": "메뉴 타입",
|
||||
"menu.type.admin": "관리자",
|
||||
"menu.type.user": "사용자",
|
||||
"status.active": "활성화",
|
||||
"status.inactive": "비활성화",
|
||||
"form.lang.key": "언어 키",
|
||||
"form.lang.key.select": "언어 키 선택",
|
||||
"form.menu.name.placeholder": "메뉴명을 입력하세요",
|
||||
"form.menu.url.placeholder": "URL을 입력하세요",
|
||||
"form.menu.description.placeholder": "설명을 입력하세요",
|
||||
"form.menu.sequence": "순서",
|
||||
"form.menu.sequence.placeholder": "순서를 입력하세요",
|
||||
"form.status.active": "활성",
|
||||
"form.status.inactive": "비활성",
|
||||
"form.company.select": "회사 선택",
|
||||
"form.company.common": "공통",
|
||||
"form.company.submenu.note": "하위메뉴는 회사별로 관리됩니다",
|
||||
"filter.company.common": "공통",
|
||||
"filter.search.placeholder": "검색어를 입력하세요",
|
||||
"modal.menu.register.title": "메뉴 등록",
|
||||
};
|
||||
|
||||
return defaultKoreanTexts[langKey] || langKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 기본 번역 텍스트 반환 (배치)
|
||||
*/
|
||||
function getDefaultTranslations(
|
||||
langKeys: string[],
|
||||
userLang: string
|
||||
): Record<string, string> {
|
||||
const result: Record<string, string> = {};
|
||||
|
||||
for (const langKey of langKeys) {
|
||||
result[langKey] = getDefaultTranslation(langKey, userLang);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 캐시 초기화 (개발/테스트용)
|
||||
*/
|
||||
export const clearCache = async (req: AuthenticatedRequest, res: Response) => {
|
||||
export const getLangText = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const beforeSize = translationCache.size;
|
||||
translationCache.clear();
|
||||
const { companyCode, langKey, langCode } = req.params;
|
||||
|
||||
logger.info("다국어 캐시 초기화 완료", {
|
||||
beforeSize,
|
||||
afterSize: 0,
|
||||
logger.info("특정 키의 다국어 텍스트 조회 요청", {
|
||||
companyCode,
|
||||
langKey,
|
||||
langCode,
|
||||
user: req.user,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
const multiLangService = new MultiLangService();
|
||||
const langText = await multiLangService.getLangText(
|
||||
companyCode,
|
||||
langKey,
|
||||
langCode
|
||||
);
|
||||
|
||||
const response: ApiResponse<string> = {
|
||||
success: true,
|
||||
message: "캐시가 초기화되었습니다.",
|
||||
beforeSize,
|
||||
afterSize: 0,
|
||||
});
|
||||
message: "특정 키의 다국어 텍스트 조회 성공",
|
||||
data: langText,
|
||||
};
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
logger.error("캐시 초기화 실패", { error });
|
||||
logger.error("특정 키의 다국어 텍스트 조회 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "캐시 초기화 중 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
message: "특정 키의 다국어 텍스트 조회 중 오류가 발생했습니다.",
|
||||
error: {
|
||||
code: "LANG_TEXT_ERROR",
|
||||
details: error instanceof Error ? error.message : "Unknown error",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* DELETE /api/multilang/languages/:langCode
|
||||
* 언어 삭제 API
|
||||
*/
|
||||
export const deleteLanguage = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { langCode } = req.params;
|
||||
logger.info("언어 삭제 요청", { langCode, user: req.user });
|
||||
|
||||
if (!langCode) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "언어 코드가 필요합니다.",
|
||||
error: {
|
||||
code: "MISSING_LANG_CODE",
|
||||
details: "langCode parameter is required",
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const multiLangService = new MultiLangService();
|
||||
await multiLangService.deleteLanguage(langCode);
|
||||
|
||||
const response: ApiResponse<string> = {
|
||||
success: true,
|
||||
message: "언어가 성공적으로 삭제되었습니다.",
|
||||
data: "삭제 완료",
|
||||
};
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
logger.error("언어 삭제 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "언어 삭제 중 오류가 발생했습니다.",
|
||||
error: {
|
||||
code: "LANGUAGE_DELETE_ERROR",
|
||||
details: error instanceof Error ? error.message : "Unknown error",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* POST /api/multilang/batch
|
||||
* 다국어 텍스트 배치 조회 API
|
||||
*/
|
||||
export const getBatchTranslations = async (
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { companyCode, menuCode, userLang } = req.query;
|
||||
const {
|
||||
langKeys,
|
||||
companyCode: bodyCompanyCode,
|
||||
menuCode: bodyMenuCode,
|
||||
userLang: bodyUserLang,
|
||||
} = req.body;
|
||||
|
||||
// query params에서 읽지 못한 경우 body에서 읽기
|
||||
const finalCompanyCode = companyCode || bodyCompanyCode;
|
||||
const finalMenuCode = menuCode || bodyMenuCode;
|
||||
const finalUserLang = userLang || bodyUserLang;
|
||||
|
||||
logger.info("다국어 텍스트 배치 조회 요청", {
|
||||
companyCode: finalCompanyCode,
|
||||
menuCode: finalMenuCode,
|
||||
userLang: finalUserLang,
|
||||
keyCount: langKeys?.length || 0,
|
||||
});
|
||||
|
||||
if (!langKeys || !Array.isArray(langKeys) || langKeys.length === 0) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "langKeys 배열이 필요합니다.",
|
||||
error: {
|
||||
code: "MISSING_LANG_KEYS",
|
||||
details: "langKeys array is required",
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!finalCompanyCode || !finalUserLang) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "companyCode와 userLang은 필수입니다.",
|
||||
error: {
|
||||
code: "MISSING_REQUIRED_PARAMS",
|
||||
details: "companyCode and userLang are required",
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const multiLangService = new MultiLangService();
|
||||
const translations = await multiLangService.getBatchTranslations({
|
||||
companyCode: finalCompanyCode as string,
|
||||
menuCode: finalMenuCode as string,
|
||||
userLang: finalUserLang as string,
|
||||
langKeys,
|
||||
});
|
||||
|
||||
const response: ApiResponse<Record<string, string>> = {
|
||||
success: true,
|
||||
message: "다국어 텍스트 배치 조회 성공",
|
||||
data: translations,
|
||||
};
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
logger.error("다국어 텍스트 배치 조회 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "다국어 텍스트 배치 조회 중 오류가 발생했습니다.",
|
||||
error: {
|
||||
code: "BATCH_TRANSLATION_ERROR",
|
||||
details: error instanceof Error ? error.message : "Unknown error",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ services:
|
|||
environment:
|
||||
- NODE_ENV=development
|
||||
- PORT=8080
|
||||
- DATABASE_URL=postgresql://postgres:postgres@postgres-erp:5432/ilshin
|
||||
- DATABASE_URL=postgresql://postgres:ph0909!!@39.117.244.52:11132/plm
|
||||
- JWT_SECRET=ilshin-plm-super-secret-jwt-key-2024
|
||||
- JWT_EXPIRES_IN=24h
|
||||
- CORS_ORIGIN=http://localhost:9771
|
||||
|
|
|
|||
Loading…
Reference in New Issue