Compare commits
2 Commits
6cac3dfa3f
...
0e89393a14
| Author | SHA1 | Date |
|---|---|---|
|
|
0e89393a14 | |
|
|
96c601a0cf |
|
|
@ -665,7 +665,7 @@ export async function getLanguageList(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 다국어 키 목록 조회 (더미 데이터)
|
* 다국어 키 목록 조회
|
||||||
*/
|
*/
|
||||||
export async function getLangKeyList(
|
export async function getLangKeyList(
|
||||||
req: AuthenticatedRequest,
|
req: AuthenticatedRequest,
|
||||||
|
|
@ -677,59 +677,61 @@ export async function getLangKeyList(
|
||||||
user: req.user,
|
user: req.user,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 더미 데이터 반환
|
// 실제 데이터베이스에서 데이터 조회
|
||||||
const langKeys = [
|
const client = new Client({
|
||||||
{
|
host: process.env.DB_HOST || "localhost",
|
||||||
keyId: 1,
|
port: parseInt(process.env.DB_PORT || "5432"),
|
||||||
companyCode: "ILSHIN",
|
database: process.env.DB_NAME || "ilshin",
|
||||||
menuName: "사용자 관리",
|
user: process.env.DB_USER || "postgres",
|
||||||
langKey: "user.management.title",
|
password: process.env.DB_PASSWORD || "postgres",
|
||||||
description: "사용자 관리 페이지 제목",
|
|
||||||
isActive: "Y",
|
|
||||||
createdDate: new Date().toISOString(),
|
|
||||||
createdBy: "admin",
|
|
||||||
updatedDate: new Date().toISOString(),
|
|
||||||
updatedBy: "admin",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
keyId: 2,
|
|
||||||
companyCode: "ILSHIN",
|
|
||||||
menuName: "메뉴 관리",
|
|
||||||
langKey: "menu.management.title",
|
|
||||||
description: "메뉴 관리 페이지 제목",
|
|
||||||
isActive: "Y",
|
|
||||||
createdDate: new Date().toISOString(),
|
|
||||||
createdBy: "admin",
|
|
||||||
updatedDate: new Date().toISOString(),
|
|
||||||
updatedBy: "admin",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
keyId: 3,
|
|
||||||
companyCode: "HUTECH",
|
|
||||||
menuName: "대시보드",
|
|
||||||
langKey: "dashboard.title",
|
|
||||||
description: "대시보드 페이지 제목",
|
|
||||||
isActive: "Y",
|
|
||||||
createdDate: new Date().toISOString(),
|
|
||||||
createdBy: "admin",
|
|
||||||
updatedDate: new Date().toISOString(),
|
|
||||||
updatedBy: "admin",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// 프론트엔드에서 기대하는 응답 형식으로 변환
|
|
||||||
const response: ApiResponse<any[]> = {
|
|
||||||
success: true,
|
|
||||||
message: "다국어 키 목록 조회 성공",
|
|
||||||
data: langKeys,
|
|
||||||
};
|
|
||||||
|
|
||||||
logger.info("다국어 키 목록 조회 성공", {
|
|
||||||
totalCount: langKeys.length,
|
|
||||||
response: response,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
res.status(200).json(response);
|
await client.connect();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query = `
|
||||||
|
SELECT
|
||||||
|
key_id as "keyId",
|
||||||
|
company_code as "companyCode",
|
||||||
|
menu_name as "menuName",
|
||||||
|
lang_key as "langKey",
|
||||||
|
description,
|
||||||
|
is_active as "isActive",
|
||||||
|
created_date as "createdDate",
|
||||||
|
created_by as "createdBy",
|
||||||
|
updated_date as "updatedDate",
|
||||||
|
updated_by as "updatedBy"
|
||||||
|
FROM multi_lang_key_master
|
||||||
|
ORDER BY company_code, menu_name, lang_key
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = await client.query(query);
|
||||||
|
const langKeys = result.rows.map((row) => ({
|
||||||
|
...row,
|
||||||
|
createdDate: row.createdDate
|
||||||
|
? new Date(row.createdDate).toISOString()
|
||||||
|
: undefined,
|
||||||
|
updatedDate: row.updatedDate
|
||||||
|
? new Date(row.updatedDate).toISOString()
|
||||||
|
: undefined,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 프론트엔드에서 기대하는 응답 형식으로 변환
|
||||||
|
const response: ApiResponse<any[]> = {
|
||||||
|
success: true,
|
||||||
|
message: "다국어 키 목록 조회 성공",
|
||||||
|
data: langKeys,
|
||||||
|
};
|
||||||
|
|
||||||
|
logger.info("다국어 키 목록 조회 성공", {
|
||||||
|
totalCount: langKeys.length,
|
||||||
|
response: response,
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(200).json(response);
|
||||||
|
} finally {
|
||||||
|
await client.end();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("다국어 키 목록 조회 실패:", error);
|
logger.error("다국어 키 목록 조회 실패:", error);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -19,17 +19,6 @@ import {
|
||||||
deleteCompany, // 회사 삭제
|
deleteCompany, // 회사 삭제
|
||||||
getUserLocale,
|
getUserLocale,
|
||||||
setUserLocale,
|
setUserLocale,
|
||||||
getLanguageList,
|
|
||||||
getLangKeyList,
|
|
||||||
getLangTextList,
|
|
||||||
saveLangTexts,
|
|
||||||
saveLangKey,
|
|
||||||
updateLangKey,
|
|
||||||
deleteLangKey,
|
|
||||||
toggleLangKeyStatus,
|
|
||||||
saveLanguage,
|
|
||||||
updateLanguage,
|
|
||||||
toggleLanguageStatus,
|
|
||||||
} from "../controllers/adminController";
|
} from "../controllers/adminController";
|
||||||
import { authenticateToken } from "../middleware/authMiddleware";
|
import { authenticateToken } from "../middleware/authMiddleware";
|
||||||
|
|
||||||
|
|
@ -67,17 +56,4 @@ router.delete("/companies/:companyCode", deleteCompany); // 회사 삭제
|
||||||
router.get("/user-locale", getUserLocale);
|
router.get("/user-locale", getUserLocale);
|
||||||
router.post("/user-locale", setUserLocale);
|
router.post("/user-locale", setUserLocale);
|
||||||
|
|
||||||
// 다국어 관리 API
|
|
||||||
router.get("/multilang/languages", getLanguageList);
|
|
||||||
router.get("/multilang/keys", getLangKeyList);
|
|
||||||
router.get("/multilang/keys/:keyId/texts", getLangTextList);
|
|
||||||
router.post("/multilang/keys/:keyId/texts", saveLangTexts);
|
|
||||||
router.post("/multilang/keys", saveLangKey);
|
|
||||||
router.put("/multilang/keys/:keyId", updateLangKey);
|
|
||||||
router.delete("/multilang/keys/:keyId", deleteLangKey);
|
|
||||||
router.put("/multilang/keys/:keyId/toggle", toggleLangKeyStatus);
|
|
||||||
router.post("/multilang/languages", saveLanguage);
|
|
||||||
router.put("/multilang/languages/:langCode", updateLanguage);
|
|
||||||
router.put("/multilang/languages/:langCode/toggle", toggleLanguageStatus);
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,50 @@
|
||||||
import { Router } from "express";
|
import express from "express";
|
||||||
import {
|
|
||||||
getUserText,
|
|
||||||
getBatchTranslations,
|
|
||||||
clearCache,
|
|
||||||
} from "../controllers/multilangController";
|
|
||||||
import { authenticateToken } from "../middleware/authMiddleware";
|
import { authenticateToken } from "../middleware/authMiddleware";
|
||||||
|
import {
|
||||||
|
// 언어 관리 API
|
||||||
|
getLanguages,
|
||||||
|
createLanguage,
|
||||||
|
updateLanguage,
|
||||||
|
toggleLanguage,
|
||||||
|
|
||||||
const router = Router();
|
// 다국어 키 관리 API
|
||||||
|
getLangKeys,
|
||||||
|
getLangTexts,
|
||||||
|
createLangKey,
|
||||||
|
updateLangKey,
|
||||||
|
deleteLangKey,
|
||||||
|
toggleLangKey,
|
||||||
|
|
||||||
// 모든 multilang 라우트에 인증 미들웨어 적용
|
// 다국어 텍스트 관리 API
|
||||||
|
saveLangTexts,
|
||||||
|
getUserText,
|
||||||
|
getLangText,
|
||||||
|
getBatchTranslations,
|
||||||
|
} from "../controllers/multilangController";
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// 모든 다국어 관리 라우트에 인증 미들웨어 적용
|
||||||
router.use(authenticateToken);
|
router.use(authenticateToken);
|
||||||
|
|
||||||
// 다국어 텍스트 API
|
// 언어 관리 API
|
||||||
router.get("/user-text/:companyCode/:menuCode/:langKey", getUserText);
|
router.get("/languages", getLanguages); // 언어 목록 조회
|
||||||
|
router.post("/languages", createLanguage); // 언어 생성
|
||||||
|
router.put("/languages/:langCode", updateLanguage); // 언어 수정
|
||||||
|
router.put("/languages/:langCode/toggle", toggleLanguage); // 언어 상태 토글
|
||||||
|
|
||||||
// 다국어 텍스트 배치 조회 API (새로운 방식)
|
// 다국어 키 관리 API
|
||||||
router.post("/batch", getBatchTranslations);
|
router.get("/keys", getLangKeys); // 다국어 키 목록 조회
|
||||||
|
router.get("/keys/:keyId/texts", getLangTexts); // 특정 키의 다국어 텍스트 조회
|
||||||
|
router.post("/keys", createLangKey); // 다국어 키 생성
|
||||||
|
router.put("/keys/:keyId", updateLangKey); // 다국어 키 수정
|
||||||
|
router.delete("/keys/:keyId", deleteLangKey); // 다국어 키 삭제
|
||||||
|
router.put("/keys/:keyId/toggle", toggleLangKey); // 다국어 키 상태 토글
|
||||||
|
|
||||||
// 캐시 초기화 API (개발/테스트용)
|
// 다국어 텍스트 관리 API
|
||||||
router.delete("/cache", clearCache);
|
router.post("/keys/:keyId/texts", saveLangTexts); // 다국어 텍스트 저장/수정
|
||||||
|
router.get("/user-text/:companyCode/:menuCode/:langKey", getUserText); // 사용자별 다국어 텍스트 조회
|
||||||
|
router.get("/text/:companyCode/:langKey/:langCode", getLangText); // 특정 키의 다국어 텍스트 조회
|
||||||
|
router.post("/batch", getBatchTranslations); // 다국어 텍스트 배치 조회
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,855 @@
|
||||||
|
import { Client } from "pg";
|
||||||
|
import { logger } from "../utils/logger";
|
||||||
|
import {
|
||||||
|
Language,
|
||||||
|
LangKey,
|
||||||
|
LangText,
|
||||||
|
CreateLanguageRequest,
|
||||||
|
UpdateLanguageRequest,
|
||||||
|
CreateLangKeyRequest,
|
||||||
|
UpdateLangKeyRequest,
|
||||||
|
SaveLangTextsRequest,
|
||||||
|
GetLangKeysParams,
|
||||||
|
GetUserTextParams,
|
||||||
|
BatchTranslationRequest,
|
||||||
|
ApiResponse,
|
||||||
|
} from "../types/multilang";
|
||||||
|
|
||||||
|
export class MultiLangService {
|
||||||
|
private client: Client;
|
||||||
|
|
||||||
|
constructor(client: Client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 언어 목록 조회
|
||||||
|
*/
|
||||||
|
async getLanguages(): Promise<Language[]> {
|
||||||
|
try {
|
||||||
|
logger.info("언어 목록 조회 시작");
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
SELECT
|
||||||
|
lang_code as "langCode",
|
||||||
|
lang_name as "langName",
|
||||||
|
lang_native as "langNative",
|
||||||
|
is_active as "isActive",
|
||||||
|
sort_order as "sortOrder",
|
||||||
|
created_date as "createdDate",
|
||||||
|
created_by as "createdBy",
|
||||||
|
updated_date as "updatedDate",
|
||||||
|
updated_by as "updatedBy"
|
||||||
|
FROM language_master
|
||||||
|
ORDER BY sort_order, lang_code
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = await this.client.query(query);
|
||||||
|
const languages = result.rows.map((row) => ({
|
||||||
|
...row,
|
||||||
|
createdDate: row.createdDate ? new Date(row.createdDate) : undefined,
|
||||||
|
updatedDate: row.updatedDate ? new Date(row.updatedDate) : undefined,
|
||||||
|
}));
|
||||||
|
|
||||||
|
logger.info(`언어 목록 조회 완료: ${languages.length}개`);
|
||||||
|
return languages;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("언어 목록 조회 중 오류 발생:", error);
|
||||||
|
throw new Error(
|
||||||
|
`언어 목록 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 언어 생성
|
||||||
|
*/
|
||||||
|
async createLanguage(languageData: CreateLanguageRequest): Promise<Language> {
|
||||||
|
try {
|
||||||
|
logger.info("언어 생성 시작", { languageData });
|
||||||
|
|
||||||
|
// 중복 체크
|
||||||
|
const duplicateCheckQuery = `
|
||||||
|
SELECT lang_code FROM language_master WHERE lang_code = $1
|
||||||
|
`;
|
||||||
|
const duplicateResult = await this.client.query(duplicateCheckQuery, [
|
||||||
|
languageData.langCode,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (duplicateResult.rows.length > 0) {
|
||||||
|
throw new Error(
|
||||||
|
`이미 존재하는 언어 코드입니다: ${languageData.langCode}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 언어 생성
|
||||||
|
const insertQuery = `
|
||||||
|
INSERT INTO language_master (
|
||||||
|
lang_code, lang_name, lang_native, is_active, sort_order,
|
||||||
|
created_date, created_by, updated_date, updated_by
|
||||||
|
) VALUES ($1, $2, $3, $4, $5, now(), $6, now(), $7)
|
||||||
|
RETURNING *
|
||||||
|
`;
|
||||||
|
|
||||||
|
const insertValues = [
|
||||||
|
languageData.langCode,
|
||||||
|
languageData.langName,
|
||||||
|
languageData.langNative,
|
||||||
|
languageData.isActive || "Y",
|
||||||
|
languageData.sortOrder || 0,
|
||||||
|
languageData.createdBy || "system",
|
||||||
|
languageData.updatedBy || "system",
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = await this.client.query(insertQuery, insertValues);
|
||||||
|
const createdLanguage = result.rows[0];
|
||||||
|
|
||||||
|
logger.info("언어 생성 완료", { langCode: createdLanguage.lang_code });
|
||||||
|
|
||||||
|
return {
|
||||||
|
langCode: createdLanguage.lang_code,
|
||||||
|
langName: createdLanguage.lang_name,
|
||||||
|
langNative: createdLanguage.lang_native,
|
||||||
|
isActive: createdLanguage.is_active,
|
||||||
|
sortOrder: createdLanguage.sort_order,
|
||||||
|
createdDate: createdLanguage.created_date
|
||||||
|
? new Date(createdLanguage.created_date)
|
||||||
|
: undefined,
|
||||||
|
createdBy: createdLanguage.created_by,
|
||||||
|
updatedDate: createdLanguage.updated_date
|
||||||
|
? new Date(createdLanguage.updated_date)
|
||||||
|
: undefined,
|
||||||
|
updatedBy: createdLanguage.updated_by,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("언어 생성 중 오류 발생:", error);
|
||||||
|
throw new Error(
|
||||||
|
`언어 생성 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 언어 수정
|
||||||
|
*/
|
||||||
|
async updateLanguage(
|
||||||
|
langCode: string,
|
||||||
|
languageData: UpdateLanguageRequest
|
||||||
|
): Promise<Language> {
|
||||||
|
try {
|
||||||
|
logger.info("언어 수정 시작", { langCode, languageData });
|
||||||
|
|
||||||
|
// 기존 언어 확인
|
||||||
|
const checkQuery = `
|
||||||
|
SELECT * FROM language_master WHERE lang_code = $1
|
||||||
|
`;
|
||||||
|
const checkResult = await this.client.query(checkQuery, [langCode]);
|
||||||
|
|
||||||
|
if (checkResult.rows.length === 0) {
|
||||||
|
throw new Error(`언어를 찾을 수 없습니다: ${langCode}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 언어 수정
|
||||||
|
const updateQuery = `
|
||||||
|
UPDATE language_master
|
||||||
|
SET
|
||||||
|
lang_name = COALESCE($2, lang_name),
|
||||||
|
lang_native = COALESCE($3, lang_native),
|
||||||
|
is_active = COALESCE($4, is_active),
|
||||||
|
sort_order = COALESCE($5, sort_order),
|
||||||
|
updated_date = now(),
|
||||||
|
updated_by = $6
|
||||||
|
WHERE lang_code = $1
|
||||||
|
RETURNING *
|
||||||
|
`;
|
||||||
|
|
||||||
|
const updateValues = [
|
||||||
|
langCode,
|
||||||
|
languageData.langName,
|
||||||
|
languageData.langNative,
|
||||||
|
languageData.isActive,
|
||||||
|
languageData.sortOrder,
|
||||||
|
languageData.updatedBy || "system",
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = await this.client.query(updateQuery, updateValues);
|
||||||
|
const updatedLanguage = result.rows[0];
|
||||||
|
|
||||||
|
logger.info("언어 수정 완료", { langCode });
|
||||||
|
|
||||||
|
return {
|
||||||
|
langCode: updatedLanguage.lang_code,
|
||||||
|
langName: updatedLanguage.lang_name,
|
||||||
|
langNative: updatedLanguage.lang_native,
|
||||||
|
isActive: updatedLanguage.is_active,
|
||||||
|
sortOrder: updatedLanguage.sort_order,
|
||||||
|
createdDate: updatedLanguage.created_date
|
||||||
|
? new Date(updatedLanguage.created_date)
|
||||||
|
: undefined,
|
||||||
|
createdBy: updatedLanguage.created_by,
|
||||||
|
updatedDate: updatedLanguage.updated_date
|
||||||
|
? new Date(updatedLanguage.updated_date)
|
||||||
|
: undefined,
|
||||||
|
updatedBy: updatedLanguage.updated_by,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("언어 수정 중 오류 발생:", error);
|
||||||
|
throw new Error(
|
||||||
|
`언어 수정 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 언어 상태 토글
|
||||||
|
*/
|
||||||
|
async toggleLanguage(langCode: string): Promise<string> {
|
||||||
|
try {
|
||||||
|
logger.info("언어 상태 토글 시작", { langCode });
|
||||||
|
|
||||||
|
// 현재 상태 조회
|
||||||
|
const currentQuery = `
|
||||||
|
SELECT is_active FROM language_master WHERE lang_code = $1
|
||||||
|
`;
|
||||||
|
const currentResult = await this.client.query(currentQuery, [langCode]);
|
||||||
|
|
||||||
|
if (currentResult.rows.length === 0) {
|
||||||
|
throw new Error(`언어를 찾을 수 없습니다: ${langCode}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentStatus = currentResult.rows[0].is_active;
|
||||||
|
const newStatus = currentStatus === "Y" ? "N" : "Y";
|
||||||
|
|
||||||
|
// 상태 업데이트
|
||||||
|
const updateQuery = `
|
||||||
|
UPDATE language_master
|
||||||
|
SET is_active = $2, updated_date = now(), updated_by = 'system'
|
||||||
|
WHERE lang_code = $1
|
||||||
|
`;
|
||||||
|
|
||||||
|
await this.client.query(updateQuery, [langCode, newStatus]);
|
||||||
|
|
||||||
|
const result = newStatus === "Y" ? "활성화" : "비활성화";
|
||||||
|
logger.info("언어 상태 토글 완료", { langCode, result });
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("언어 상태 토글 중 오류 발생:", error);
|
||||||
|
throw new Error(
|
||||||
|
`언어 상태 토글 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 다국어 키 목록 조회
|
||||||
|
*/
|
||||||
|
async getLangKeys(params: GetLangKeysParams): Promise<LangKey[]> {
|
||||||
|
try {
|
||||||
|
logger.info("다국어 키 목록 조회 시작", { params });
|
||||||
|
|
||||||
|
let query = `
|
||||||
|
SELECT
|
||||||
|
key_id as "keyId",
|
||||||
|
company_code as "companyCode",
|
||||||
|
menu_name as "menuName",
|
||||||
|
lang_key as "langKey",
|
||||||
|
description,
|
||||||
|
is_active as "isActive",
|
||||||
|
created_date as "createdDate",
|
||||||
|
created_by as "createdBy",
|
||||||
|
updated_date as "updatedDate",
|
||||||
|
updated_by as "updatedBy"
|
||||||
|
FROM multi_lang_key_master
|
||||||
|
WHERE 1=1
|
||||||
|
`;
|
||||||
|
|
||||||
|
const queryParams: any[] = [];
|
||||||
|
let paramIndex = 1;
|
||||||
|
|
||||||
|
// 회사 코드 필터
|
||||||
|
if (params.companyCode) {
|
||||||
|
query += ` AND company_code = $${paramIndex}`;
|
||||||
|
queryParams.push(params.companyCode);
|
||||||
|
paramIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 메뉴 코드 필터
|
||||||
|
if (params.menuCode) {
|
||||||
|
query += ` AND menu_name = $${paramIndex}`;
|
||||||
|
queryParams.push(params.menuCode);
|
||||||
|
paramIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 검색 조건
|
||||||
|
if (params.searchText) {
|
||||||
|
query += ` AND (
|
||||||
|
lang_key ILIKE $${paramIndex} OR
|
||||||
|
description ILIKE $${paramIndex} OR
|
||||||
|
menu_name ILIKE $${paramIndex}
|
||||||
|
)`;
|
||||||
|
queryParams.push(`%${params.searchText}%`);
|
||||||
|
paramIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 정렬
|
||||||
|
query += ` ORDER BY company_code, menu_name, lang_key`;
|
||||||
|
|
||||||
|
const result = await this.client.query(query, queryParams);
|
||||||
|
const langKeys = result.rows.map((row) => ({
|
||||||
|
...row,
|
||||||
|
createdDate: row.createdDate ? new Date(row.createdDate) : undefined,
|
||||||
|
updatedDate: row.updatedDate ? new Date(row.updatedDate) : undefined,
|
||||||
|
}));
|
||||||
|
|
||||||
|
logger.info(`다국어 키 목록 조회 완료: ${langKeys.length}개`);
|
||||||
|
return langKeys;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("다국어 키 목록 조회 중 오류 발생:", error);
|
||||||
|
throw new Error(
|
||||||
|
`다국어 키 목록 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 특정 키의 다국어 텍스트 조회
|
||||||
|
*/
|
||||||
|
async getLangTexts(keyId: number): Promise<LangText[]> {
|
||||||
|
try {
|
||||||
|
logger.info("다국어 텍스트 조회 시작", { keyId });
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
SELECT
|
||||||
|
text_id as "textId",
|
||||||
|
key_id as "keyId",
|
||||||
|
lang_code as "langCode",
|
||||||
|
lang_text as "langText",
|
||||||
|
is_active as "isActive",
|
||||||
|
created_date as "createdDate",
|
||||||
|
created_by as "createdBy",
|
||||||
|
updated_date as "updatedDate",
|
||||||
|
updated_by as "updatedBy"
|
||||||
|
FROM multi_lang_text
|
||||||
|
WHERE key_id = $1 AND is_active = 'Y'
|
||||||
|
ORDER BY lang_code
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = await this.client.query(query, [keyId]);
|
||||||
|
const langTexts = result.rows.map((row) => ({
|
||||||
|
...row,
|
||||||
|
createdDate: row.createdDate ? new Date(row.createdDate) : undefined,
|
||||||
|
updatedDate: row.updatedDate ? new Date(row.updatedDate) : undefined,
|
||||||
|
}));
|
||||||
|
|
||||||
|
logger.info(`다국어 텍스트 조회 완료: ${langTexts.length}개`);
|
||||||
|
return langTexts;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("다국어 텍스트 조회 중 오류 발생:", error);
|
||||||
|
throw new Error(
|
||||||
|
`다국어 텍스트 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 다국어 키 생성
|
||||||
|
*/
|
||||||
|
async createLangKey(keyData: CreateLangKeyRequest): Promise<number> {
|
||||||
|
try {
|
||||||
|
logger.info("다국어 키 생성 시작", { keyData });
|
||||||
|
|
||||||
|
// 중복 체크
|
||||||
|
const duplicateCheckQuery = `
|
||||||
|
SELECT key_id FROM multi_lang_key_master
|
||||||
|
WHERE company_code = $1 AND lang_key = $2
|
||||||
|
`;
|
||||||
|
const duplicateResult = await this.client.query(duplicateCheckQuery, [
|
||||||
|
keyData.companyCode,
|
||||||
|
keyData.langKey,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (duplicateResult.rows.length > 0) {
|
||||||
|
throw new Error(
|
||||||
|
`동일한 회사에 이미 존재하는 언어키입니다: ${keyData.langKey}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 다국어 키 생성
|
||||||
|
const insertQuery = `
|
||||||
|
INSERT INTO multi_lang_key_master (
|
||||||
|
company_code, menu_name, lang_key, description, is_active,
|
||||||
|
created_date, created_by, updated_date, updated_by
|
||||||
|
) VALUES ($1, $2, $3, $4, $5, now(), $6, now(), $7)
|
||||||
|
RETURNING key_id
|
||||||
|
`;
|
||||||
|
|
||||||
|
const insertValues = [
|
||||||
|
keyData.companyCode,
|
||||||
|
keyData.menuName || null,
|
||||||
|
keyData.langKey,
|
||||||
|
keyData.description || null,
|
||||||
|
keyData.isActive || "Y",
|
||||||
|
keyData.createdBy || "system",
|
||||||
|
keyData.updatedBy || "system",
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = await this.client.query(insertQuery, insertValues);
|
||||||
|
const keyId = result.rows[0].key_id;
|
||||||
|
|
||||||
|
logger.info("다국어 키 생성 완료", { keyId, langKey: keyData.langKey });
|
||||||
|
|
||||||
|
return keyId;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("다국어 키 생성 중 오류 발생:", error);
|
||||||
|
throw new Error(
|
||||||
|
`다국어 키 생성 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 다국어 키 수정
|
||||||
|
*/
|
||||||
|
async updateLangKey(
|
||||||
|
keyId: number,
|
||||||
|
keyData: UpdateLangKeyRequest
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
logger.info("다국어 키 수정 시작", { keyId, keyData });
|
||||||
|
|
||||||
|
// 기존 키 확인
|
||||||
|
const checkQuery = `
|
||||||
|
SELECT key_id FROM multi_lang_key_master WHERE key_id = $1
|
||||||
|
`;
|
||||||
|
const checkResult = await this.client.query(checkQuery, [keyId]);
|
||||||
|
|
||||||
|
if (checkResult.rows.length === 0) {
|
||||||
|
throw new Error(`다국어 키를 찾을 수 없습니다: ${keyId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 중복 체크 (자신을 제외하고)
|
||||||
|
if (keyData.companyCode && keyData.langKey) {
|
||||||
|
const duplicateCheckQuery = `
|
||||||
|
SELECT key_id FROM multi_lang_key_master
|
||||||
|
WHERE company_code = $1 AND lang_key = $2 AND key_id != $3
|
||||||
|
`;
|
||||||
|
const duplicateResult = await this.client.query(duplicateCheckQuery, [
|
||||||
|
keyData.companyCode,
|
||||||
|
keyData.langKey,
|
||||||
|
keyId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (duplicateResult.rows.length > 0) {
|
||||||
|
throw new Error(
|
||||||
|
`동일한 회사에 이미 존재하는 언어키입니다: ${keyData.langKey}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 다국어 키 수정
|
||||||
|
const updateQuery = `
|
||||||
|
UPDATE multi_lang_key_master
|
||||||
|
SET
|
||||||
|
company_code = COALESCE($2, company_code),
|
||||||
|
menu_name = COALESCE($3, menu_name),
|
||||||
|
lang_key = COALESCE($4, lang_key),
|
||||||
|
description = COALESCE($5, description),
|
||||||
|
updated_date = now(),
|
||||||
|
updated_by = $6
|
||||||
|
WHERE key_id = $1
|
||||||
|
`;
|
||||||
|
|
||||||
|
const updateValues = [
|
||||||
|
keyId,
|
||||||
|
keyData.companyCode,
|
||||||
|
keyData.menuName,
|
||||||
|
keyData.langKey,
|
||||||
|
keyData.description,
|
||||||
|
keyData.updatedBy || "system",
|
||||||
|
];
|
||||||
|
|
||||||
|
await this.client.query(updateQuery, updateValues);
|
||||||
|
|
||||||
|
logger.info("다국어 키 수정 완료", { keyId });
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("다국어 키 수정 중 오류 발생:", error);
|
||||||
|
throw new Error(
|
||||||
|
`다국어 키 수정 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 다국어 키 삭제
|
||||||
|
*/
|
||||||
|
async deleteLangKey(keyId: number): Promise<void> {
|
||||||
|
try {
|
||||||
|
logger.info("다국어 키 삭제 시작", { keyId });
|
||||||
|
|
||||||
|
// 기존 키 확인
|
||||||
|
const checkQuery = `
|
||||||
|
SELECT key_id FROM multi_lang_key_master WHERE key_id = $1
|
||||||
|
`;
|
||||||
|
const checkResult = await this.client.query(checkQuery, [keyId]);
|
||||||
|
|
||||||
|
if (checkResult.rows.length === 0) {
|
||||||
|
throw new Error(`다국어 키를 찾을 수 없습니다: ${keyId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 트랜잭션 시작
|
||||||
|
await this.client.query("BEGIN");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 관련된 다국어 텍스트 삭제
|
||||||
|
const deleteTextsQuery = `
|
||||||
|
DELETE FROM multi_lang_text WHERE key_id = $1
|
||||||
|
`;
|
||||||
|
await this.client.query(deleteTextsQuery, [keyId]);
|
||||||
|
|
||||||
|
// 다국어 키 삭제
|
||||||
|
const deleteKeyQuery = `
|
||||||
|
DELETE FROM multi_lang_key_master WHERE key_id = $1
|
||||||
|
`;
|
||||||
|
await this.client.query(deleteKeyQuery, [keyId]);
|
||||||
|
|
||||||
|
// 트랜잭션 커밋
|
||||||
|
await this.client.query("COMMIT");
|
||||||
|
|
||||||
|
logger.info("다국어 키 삭제 완료", { keyId });
|
||||||
|
} catch (error) {
|
||||||
|
// 트랜잭션 롤백
|
||||||
|
await this.client.query("ROLLBACK");
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("다국어 키 삭제 중 오류 발생:", error);
|
||||||
|
throw new Error(
|
||||||
|
`다국어 키 삭제 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 다국어 키 상태 토글
|
||||||
|
*/
|
||||||
|
async toggleLangKey(keyId: number): Promise<string> {
|
||||||
|
try {
|
||||||
|
logger.info("다국어 키 상태 토글 시작", { keyId });
|
||||||
|
|
||||||
|
// 현재 상태 조회
|
||||||
|
const currentQuery = `
|
||||||
|
SELECT is_active FROM multi_lang_key_master WHERE key_id = $1
|
||||||
|
`;
|
||||||
|
const currentResult = await this.client.query(currentQuery, [keyId]);
|
||||||
|
|
||||||
|
if (currentResult.rows.length === 0) {
|
||||||
|
throw new Error(`다국어 키를 찾을 수 없습니다: ${keyId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentStatus = currentResult.rows[0].is_active;
|
||||||
|
const newStatus = currentStatus === "Y" ? "N" : "Y";
|
||||||
|
|
||||||
|
// 상태 업데이트
|
||||||
|
const updateQuery = `
|
||||||
|
UPDATE multi_lang_key_master
|
||||||
|
SET is_active = $2, updated_date = now(), updated_by = 'system'
|
||||||
|
WHERE key_id = $1
|
||||||
|
`;
|
||||||
|
|
||||||
|
await this.client.query(updateQuery, [keyId, newStatus]);
|
||||||
|
|
||||||
|
const result = newStatus === "Y" ? "활성화" : "비활성화";
|
||||||
|
logger.info("다국어 키 상태 토글 완료", { keyId, result });
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("다국어 키 상태 토글 중 오류 발생:", error);
|
||||||
|
throw new Error(
|
||||||
|
`다국어 키 상태 토글 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 다국어 텍스트 저장/수정
|
||||||
|
*/
|
||||||
|
async saveLangTexts(
|
||||||
|
keyId: number,
|
||||||
|
textData: SaveLangTextsRequest
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
logger.info("다국어 텍스트 저장 시작", {
|
||||||
|
keyId,
|
||||||
|
textCount: textData.texts.length,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 기존 키 확인
|
||||||
|
const checkQuery = `
|
||||||
|
SELECT key_id FROM multi_lang_key_master WHERE key_id = $1
|
||||||
|
`;
|
||||||
|
const checkResult = await this.client.query(checkQuery, [keyId]);
|
||||||
|
|
||||||
|
if (checkResult.rows.length === 0) {
|
||||||
|
throw new Error(`다국어 키를 찾을 수 없습니다: ${keyId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 트랜잭션 시작
|
||||||
|
await this.client.query("BEGIN");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 기존 텍스트 삭제
|
||||||
|
const deleteTextsQuery = `
|
||||||
|
DELETE FROM multi_lang_text WHERE key_id = $1
|
||||||
|
`;
|
||||||
|
await this.client.query(deleteTextsQuery, [keyId]);
|
||||||
|
|
||||||
|
// 새로운 텍스트 삽입
|
||||||
|
for (const text of textData.texts) {
|
||||||
|
const insertTextQuery = `
|
||||||
|
INSERT INTO multi_lang_text (
|
||||||
|
key_id, lang_code, lang_text, is_active,
|
||||||
|
created_date, created_by, updated_date, updated_by
|
||||||
|
) VALUES ($1, $2, $3, $4, now(), $5, now(), $6)
|
||||||
|
`;
|
||||||
|
|
||||||
|
const insertValues = [
|
||||||
|
keyId,
|
||||||
|
text.langCode,
|
||||||
|
text.langText,
|
||||||
|
text.isActive || "Y",
|
||||||
|
text.createdBy || "system",
|
||||||
|
text.updatedBy || "system",
|
||||||
|
];
|
||||||
|
|
||||||
|
await this.client.query(insertTextQuery, insertValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 트랜잭션 커밋
|
||||||
|
await this.client.query("COMMIT");
|
||||||
|
|
||||||
|
logger.info("다국어 텍스트 저장 완료", {
|
||||||
|
keyId,
|
||||||
|
savedCount: textData.texts.length,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// 트랜잭션 롤백
|
||||||
|
await this.client.query("ROLLBACK");
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("다국어 텍스트 저장 중 오류 발생:", error);
|
||||||
|
throw new Error(
|
||||||
|
`다국어 텍스트 저장 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사용자별 다국어 텍스트 조회
|
||||||
|
*/
|
||||||
|
async getUserText(params: GetUserTextParams): Promise<string> {
|
||||||
|
try {
|
||||||
|
logger.info("사용자별 다국어 텍스트 조회 시작", { params });
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
SELECT t.lang_text as "langText"
|
||||||
|
FROM multi_lang_key_master km
|
||||||
|
JOIN multi_lang_text t ON km.key_id = t.key_id
|
||||||
|
WHERE km.company_code = $1
|
||||||
|
AND km.menu_name = $2
|
||||||
|
AND km.lang_key = $3
|
||||||
|
AND t.lang_code = $4
|
||||||
|
AND km.is_active = 'Y'
|
||||||
|
AND t.is_active = 'Y'
|
||||||
|
LIMIT 1
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = await this.client.query(query, [
|
||||||
|
params.companyCode,
|
||||||
|
params.menuCode,
|
||||||
|
params.langKey,
|
||||||
|
params.userLang,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (result.rows.length === 0) {
|
||||||
|
logger.warn("사용자별 다국어 텍스트를 찾을 수 없음", { params });
|
||||||
|
return params.langKey; // 기본값으로 키 반환
|
||||||
|
}
|
||||||
|
|
||||||
|
const langText = result.rows[0].langText;
|
||||||
|
logger.info("사용자별 다국어 텍스트 조회 완료", { params, langText });
|
||||||
|
|
||||||
|
return langText;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("사용자별 다국어 텍스트 조회 중 오류 발생:", error);
|
||||||
|
throw new Error(
|
||||||
|
`사용자별 다국어 텍스트 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 특정 키의 다국어 텍스트 조회
|
||||||
|
*/
|
||||||
|
async getLangText(
|
||||||
|
companyCode: string,
|
||||||
|
langKey: string,
|
||||||
|
langCode: string
|
||||||
|
): Promise<string> {
|
||||||
|
try {
|
||||||
|
logger.info("특정 키의 다국어 텍스트 조회 시작", {
|
||||||
|
companyCode,
|
||||||
|
langKey,
|
||||||
|
langCode,
|
||||||
|
});
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
SELECT t.lang_text as "langText"
|
||||||
|
FROM multi_lang_text t
|
||||||
|
JOIN multi_lang_key_master k ON t.key_id = k.key_id
|
||||||
|
WHERE k.company_code = $1
|
||||||
|
AND k.lang_key = $2
|
||||||
|
AND t.lang_code = $3
|
||||||
|
AND t.is_active = 'Y'
|
||||||
|
AND k.is_active = 'Y'
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = await this.client.query(query, [
|
||||||
|
companyCode,
|
||||||
|
langKey,
|
||||||
|
langCode,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (result.rows.length === 0) {
|
||||||
|
logger.warn("특정 키의 다국어 텍스트를 찾을 수 없음", {
|
||||||
|
companyCode,
|
||||||
|
langKey,
|
||||||
|
langCode,
|
||||||
|
});
|
||||||
|
return langKey; // 기본값으로 키 반환
|
||||||
|
}
|
||||||
|
|
||||||
|
const langText = result.rows[0].langText;
|
||||||
|
logger.info("특정 키의 다국어 텍스트 조회 완료", {
|
||||||
|
companyCode,
|
||||||
|
langKey,
|
||||||
|
langCode,
|
||||||
|
langText,
|
||||||
|
});
|
||||||
|
|
||||||
|
return langText;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("특정 키의 다국어 텍스트 조회 중 오류 발생:", error);
|
||||||
|
throw new Error(
|
||||||
|
`특정 키의 다국어 텍스트 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 배치 번역 조회
|
||||||
|
*/
|
||||||
|
async getBatchTranslations(
|
||||||
|
params: BatchTranslationRequest
|
||||||
|
): Promise<Record<string, string>> {
|
||||||
|
try {
|
||||||
|
logger.info("배치 번역 조회 시작", {
|
||||||
|
companyCode: params.companyCode,
|
||||||
|
menuCode: params.menuCode,
|
||||||
|
userLang: params.userLang,
|
||||||
|
keyCount: params.langKeys.length,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (params.langKeys.length === 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 모든 키에 대한 마스터 정보를 한번에 조회
|
||||||
|
const langKeyMastersQuery = `
|
||||||
|
SELECT key_id, lang_key, company_code
|
||||||
|
FROM multi_lang_key_master
|
||||||
|
WHERE lang_key = ANY($1::varchar[])
|
||||||
|
AND (company_code = $2::varchar OR company_code = '*')
|
||||||
|
ORDER BY
|
||||||
|
CASE WHEN company_code = $2::varchar THEN 1 ELSE 2 END,
|
||||||
|
lang_key,
|
||||||
|
company_code
|
||||||
|
`;
|
||||||
|
|
||||||
|
const langKeyMasters = await this.client.query(langKeyMastersQuery, [
|
||||||
|
params.langKeys,
|
||||||
|
params.companyCode,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (langKeyMasters.rows.length === 0) {
|
||||||
|
logger.warn("배치 번역: 언어키 마스터를 찾을 수 없음", { params });
|
||||||
|
return this.createDefaultTranslations(params.langKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 찾은 키들의 ID 목록
|
||||||
|
const keyIds = langKeyMasters.rows.map((row) => row.key_id);
|
||||||
|
const foundKeys = langKeyMasters.rows.map((row) => row.lang_key);
|
||||||
|
|
||||||
|
// 누락된 키들 (기본값으로 설정)
|
||||||
|
const missingKeys = params.langKeys.filter(
|
||||||
|
(key) => !foundKeys.includes(key)
|
||||||
|
);
|
||||||
|
const result: Record<string, string> = {};
|
||||||
|
|
||||||
|
// 기본값으로 누락된 키들 설정
|
||||||
|
missingKeys.forEach((key) => {
|
||||||
|
result[key] = key;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 실제 번역 텍스트 조회
|
||||||
|
if (keyIds.length > 0) {
|
||||||
|
const textsQuery = `
|
||||||
|
SELECT t.key_id, t.lang_text, km.lang_key
|
||||||
|
FROM multi_lang_text t
|
||||||
|
JOIN multi_lang_key_master km ON t.key_id = km.key_id
|
||||||
|
WHERE t.key_id = ANY($1::int[])
|
||||||
|
AND t.lang_code = $2
|
||||||
|
AND t.is_active = 'Y'
|
||||||
|
`;
|
||||||
|
|
||||||
|
const texts = await this.client.query(textsQuery, [
|
||||||
|
keyIds,
|
||||||
|
params.userLang,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 결과 매핑
|
||||||
|
texts.rows.forEach((row) => {
|
||||||
|
result[row.lang_key] = row.lang_text;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("배치 번역 조회 완료", {
|
||||||
|
totalKeys: params.langKeys.length,
|
||||||
|
foundKeys: foundKeys.length,
|
||||||
|
missingKeys: missingKeys.length,
|
||||||
|
resultKeys: Object.keys(result).length,
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("배치 번역 조회 중 오류 발생:", error);
|
||||||
|
throw new Error(
|
||||||
|
`배치 번역 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 기본 번역 생성 (키를 그대로 반환)
|
||||||
|
*/
|
||||||
|
private createDefaultTranslations(
|
||||||
|
langKeys: string[]
|
||||||
|
): Record<string, string> {
|
||||||
|
const result: Record<string, string> = {};
|
||||||
|
langKeys.forEach((key) => {
|
||||||
|
result[key] = key;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
export interface Language {
|
||||||
|
langCode: string;
|
||||||
|
langName: string;
|
||||||
|
langNative: string;
|
||||||
|
isActive: string;
|
||||||
|
sortOrder?: number;
|
||||||
|
createdDate?: Date;
|
||||||
|
createdBy?: string;
|
||||||
|
updatedDate?: Date;
|
||||||
|
updatedBy?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LangKey {
|
||||||
|
keyId?: number;
|
||||||
|
companyCode: string;
|
||||||
|
menuName?: string;
|
||||||
|
langKey: string;
|
||||||
|
description?: string;
|
||||||
|
isActive: string;
|
||||||
|
createdDate?: Date;
|
||||||
|
createdBy?: string;
|
||||||
|
updatedDate?: Date;
|
||||||
|
updatedBy?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LangText {
|
||||||
|
textId?: number;
|
||||||
|
keyId: number;
|
||||||
|
langCode: string;
|
||||||
|
langText: string;
|
||||||
|
isActive: string;
|
||||||
|
createdDate?: Date;
|
||||||
|
createdBy?: string;
|
||||||
|
updatedDate?: Date;
|
||||||
|
updatedBy?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LangKeyWithTexts extends LangKey {
|
||||||
|
texts: LangText[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateLanguageRequest {
|
||||||
|
langCode: string;
|
||||||
|
langName: string;
|
||||||
|
langNative: string;
|
||||||
|
isActive?: string;
|
||||||
|
sortOrder?: number;
|
||||||
|
createdBy?: string;
|
||||||
|
updatedBy?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateLanguageRequest {
|
||||||
|
langName?: string;
|
||||||
|
langNative?: string;
|
||||||
|
isActive?: string;
|
||||||
|
sortOrder?: number;
|
||||||
|
updatedBy?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateLangKeyRequest {
|
||||||
|
companyCode: string;
|
||||||
|
menuName?: string;
|
||||||
|
langKey: string;
|
||||||
|
description?: string;
|
||||||
|
isActive?: string;
|
||||||
|
createdBy?: string;
|
||||||
|
updatedBy?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateLangKeyRequest {
|
||||||
|
companyCode?: string;
|
||||||
|
menuName?: string;
|
||||||
|
langKey?: string;
|
||||||
|
description?: string;
|
||||||
|
updatedBy?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SaveLangTextsRequest {
|
||||||
|
texts: Array<{
|
||||||
|
langCode: string;
|
||||||
|
langText: string;
|
||||||
|
isActive?: string;
|
||||||
|
createdBy?: string;
|
||||||
|
updatedBy?: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetLangKeysParams {
|
||||||
|
companyCode?: string;
|
||||||
|
menuCode?: string;
|
||||||
|
keyType?: string;
|
||||||
|
searchText?: string;
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetUserTextParams {
|
||||||
|
companyCode: string;
|
||||||
|
menuCode: string;
|
||||||
|
langKey: string;
|
||||||
|
userLang: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BatchTranslationRequest {
|
||||||
|
companyCode: string;
|
||||||
|
menuCode?: string;
|
||||||
|
userLang: string;
|
||||||
|
langKeys: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TranslationCacheEntry {
|
||||||
|
data: Record<string, string>;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiResponse<T = any> {
|
||||||
|
success: boolean;
|
||||||
|
message?: string;
|
||||||
|
data?: T;
|
||||||
|
error?: {
|
||||||
|
code: string;
|
||||||
|
details?: any;
|
||||||
|
};
|
||||||
|
pagination?: {
|
||||||
|
page: number;
|
||||||
|
limit: number;
|
||||||
|
total: number;
|
||||||
|
totalPages: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -65,7 +65,7 @@ export default function MultiLangPage() {
|
||||||
const fetchCompanies = async () => {
|
const fetchCompanies = async () => {
|
||||||
try {
|
try {
|
||||||
console.log("회사 목록 조회 시작");
|
console.log("회사 목록 조회 시작");
|
||||||
const response = await apiClient.get("/api/admin/companies");
|
const response = await apiClient.get("/admin/companies");
|
||||||
console.log("회사 목록 응답 데이터:", response.data);
|
console.log("회사 목록 응답 데이터:", response.data);
|
||||||
|
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
|
|
@ -87,7 +87,7 @@ export default function MultiLangPage() {
|
||||||
// 언어 목록 조회
|
// 언어 목록 조회
|
||||||
const fetchLanguages = async () => {
|
const fetchLanguages = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await apiClient.get("/api/admin/multilang/languages");
|
const response = await apiClient.get("/multilang/languages");
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
setLanguages(data.data);
|
setLanguages(data.data);
|
||||||
|
|
@ -100,7 +100,7 @@ export default function MultiLangPage() {
|
||||||
// 다국어 키 목록 조회
|
// 다국어 키 목록 조회
|
||||||
const fetchLangKeys = async () => {
|
const fetchLangKeys = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await apiClient.get("/api/admin/multilang/keys");
|
const response = await apiClient.get("/multilang/keys");
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
console.log("✅ 전체 키 목록 로드:", data.data.length, "개");
|
console.log("✅ 전체 키 목록 로드:", data.data.length, "개");
|
||||||
|
|
@ -147,7 +147,7 @@ export default function MultiLangPage() {
|
||||||
const fetchLangTexts = async (keyId: number) => {
|
const fetchLangTexts = async (keyId: number) => {
|
||||||
try {
|
try {
|
||||||
console.log("다국어 텍스트 조회 시작: keyId =", keyId);
|
console.log("다국어 텍스트 조회 시작: keyId =", keyId);
|
||||||
const response = await apiClient.get(`/api/admin/multilang/keys/${keyId}/texts`);
|
const response = await apiClient.get(`/multilang/keys/${keyId}/texts`);
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
console.log("다국어 텍스트 조회 응답:", data);
|
console.log("다국어 텍스트 조회 응답:", data);
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
|
|
@ -203,7 +203,18 @@ export default function MultiLangPage() {
|
||||||
if (!selectedKey) return;
|
if (!selectedKey) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await apiClient.post(`/api/admin/multilang/keys/${selectedKey.keyId}/texts`, editingTexts);
|
// 백엔드가 기대하는 형식으로 데이터 변환
|
||||||
|
const requestData = {
|
||||||
|
texts: editingTexts.map((text) => ({
|
||||||
|
langCode: text.langCode,
|
||||||
|
langText: text.langText,
|
||||||
|
isActive: text.isActive || "Y",
|
||||||
|
createdBy: user?.userId || "system",
|
||||||
|
updatedBy: user?.userId || "system",
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await apiClient.post(`/multilang/keys/${selectedKey.keyId}/texts`, requestData);
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
alert("저장되었습니다.");
|
alert("저장되었습니다.");
|
||||||
|
|
@ -245,9 +256,9 @@ export default function MultiLangPage() {
|
||||||
|
|
||||||
let response;
|
let response;
|
||||||
if (editingLanguage) {
|
if (editingLanguage) {
|
||||||
response = await apiClient.put(`/api/admin/multilang/languages/${editingLanguage.langCode}`, requestData);
|
response = await apiClient.put(`/multilang/languages/${editingLanguage.langCode}`, requestData);
|
||||||
} else {
|
} else {
|
||||||
response = await apiClient.post("/api/admin/multilang/languages", requestData);
|
response = await apiClient.post("/multilang/languages", requestData);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = response.data;
|
const result = response.data;
|
||||||
|
|
@ -282,17 +293,11 @@ export default function MultiLangPage() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const deletePromises = Array.from(selectedLanguages).map((langCode) =>
|
const deletePromises = Array.from(selectedLanguages).map((langCode) =>
|
||||||
fetch(`${API_BASE_URL}/multilang/languages/${langCode}`, {
|
apiClient.delete(`/admin/multilang/languages/${langCode}`),
|
||||||
method: "DELETE",
|
|
||||||
credentials: "include",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const responses = await Promise.all(deletePromises);
|
const responses = await Promise.all(deletePromises);
|
||||||
const failedDeletes = responses.filter((response) => !response.ok);
|
const failedDeletes = responses.filter((response) => !response.data.success);
|
||||||
|
|
||||||
if (failedDeletes.length === 0) {
|
if (failedDeletes.length === 0) {
|
||||||
alert("선택된 언어가 삭제되었습니다.");
|
alert("선택된 언어가 삭제되었습니다.");
|
||||||
|
|
@ -344,9 +349,9 @@ export default function MultiLangPage() {
|
||||||
|
|
||||||
let response;
|
let response;
|
||||||
if (editingKey) {
|
if (editingKey) {
|
||||||
response = await apiClient.put(`/api/admin/multilang/keys/${editingKey.keyId}`, requestData);
|
response = await apiClient.put(`/multilang/keys/${editingKey.keyId}`, requestData);
|
||||||
} else {
|
} else {
|
||||||
response = await apiClient.post("/api/admin/multilang/keys", requestData);
|
response = await apiClient.post("/multilang/keys", requestData);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
|
|
@ -383,7 +388,7 @@ export default function MultiLangPage() {
|
||||||
// 키 상태 토글
|
// 키 상태 토글
|
||||||
const handleToggleStatus = async (keyId: number) => {
|
const handleToggleStatus = async (keyId: number) => {
|
||||||
try {
|
try {
|
||||||
const response = await apiClient.put(`/api/admin/multilang/keys/${keyId}/toggle`);
|
const response = await apiClient.put(`/multilang/keys/${keyId}/toggle`);
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
alert(`키가 ${data.data}되었습니다.`);
|
alert(`키가 ${data.data}되었습니다.`);
|
||||||
|
|
@ -400,7 +405,7 @@ export default function MultiLangPage() {
|
||||||
// 언어 상태 토글
|
// 언어 상태 토글
|
||||||
const handleToggleLanguageStatus = async (langCode: string) => {
|
const handleToggleLanguageStatus = async (langCode: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await apiClient.put(`/api/admin/multilang/languages/${langCode}/toggle`);
|
const response = await apiClient.put(`/multilang/languages/${langCode}/toggle`);
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
alert(`언어가 ${data.data}되었습니다.`);
|
alert(`언어가 ${data.data}되었습니다.`);
|
||||||
|
|
@ -440,9 +445,7 @@ export default function MultiLangPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const deletePromises = Array.from(selectedKeys).map((keyId) =>
|
const deletePromises = Array.from(selectedKeys).map((keyId) => apiClient.delete(`/multilang/keys/${keyId}`));
|
||||||
apiClient.delete(`/api/admin/multilang/keys/${keyId}`),
|
|
||||||
);
|
|
||||||
|
|
||||||
const responses = await Promise.all(deletePromises);
|
const responses = await Promise.all(deletePromises);
|
||||||
const allSuccess = responses.every((response) => response.data.success);
|
const allSuccess = responses.every((response) => response.data.success);
|
||||||
|
|
@ -472,7 +475,7 @@ export default function MultiLangPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await apiClient.delete(`/api/admin/multilang/keys/${keyId}`);
|
const response = await apiClient.delete(`/multilang/keys/${keyId}`);
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
alert("언어 키가 영구적으로 삭제되었습니다.");
|
alert("언어 키가 영구적으로 삭제되었습니다.");
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import axios, { AxiosResponse, AxiosError } from "axios";
|
import axios, { AxiosResponse, AxiosError } from "axios";
|
||||||
|
|
||||||
// API 기본 URL 설정
|
// API 기본 URL 설정
|
||||||
export const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080";
|
export const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080/api";
|
||||||
|
|
||||||
// JWT 토큰 관리 유틸리티
|
// JWT 토큰 관리 유틸리티
|
||||||
const TokenManager = {
|
const TokenManager = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue