856 lines
25 KiB
TypeScript
856 lines
25 KiB
TypeScript
|
|
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;
|
||
|
|
}
|
||
|
|
}
|