feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
import { query, queryOne, transaction } from "../database/db";
|
2025-08-25 15:12:31 +09:00
|
|
|
import { logger } from "../utils/logger";
|
|
|
|
|
import {
|
|
|
|
|
Language,
|
|
|
|
|
LangKey,
|
|
|
|
|
LangText,
|
2026-01-13 18:28:11 +09:00
|
|
|
LangCategory,
|
2025-08-25 15:12:31 +09:00
|
|
|
CreateLanguageRequest,
|
|
|
|
|
UpdateLanguageRequest,
|
|
|
|
|
CreateLangKeyRequest,
|
|
|
|
|
UpdateLangKeyRequest,
|
|
|
|
|
SaveLangTextsRequest,
|
|
|
|
|
GetLangKeysParams,
|
|
|
|
|
GetUserTextParams,
|
|
|
|
|
BatchTranslationRequest,
|
2026-01-13 18:28:11 +09:00
|
|
|
GenerateKeyRequest,
|
|
|
|
|
CreateOverrideKeyRequest,
|
2025-08-25 15:12:31 +09:00
|
|
|
ApiResponse,
|
|
|
|
|
} from "../types/multilang";
|
|
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
export class MultiLangService {
|
|
|
|
|
constructor() {}
|
2025-08-25 15:12:31 +09:00
|
|
|
|
2026-01-13 18:28:11 +09:00
|
|
|
// =====================================================
|
|
|
|
|
// 카테고리 관련 메서드
|
|
|
|
|
// =====================================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 카테고리 목록 조회 (트리 구조)
|
|
|
|
|
*/
|
|
|
|
|
async getCategories(): Promise<LangCategory[]> {
|
|
|
|
|
try {
|
|
|
|
|
logger.info("카테고리 목록 조회 시작");
|
|
|
|
|
|
|
|
|
|
const categories = await query<{
|
|
|
|
|
category_id: number;
|
|
|
|
|
category_code: string;
|
|
|
|
|
category_name: string;
|
|
|
|
|
parent_id: number | null;
|
|
|
|
|
level: number;
|
|
|
|
|
key_prefix: string;
|
|
|
|
|
description: string | null;
|
|
|
|
|
sort_order: number;
|
|
|
|
|
is_active: string;
|
|
|
|
|
}>(
|
|
|
|
|
`SELECT category_id, category_code, category_name, parent_id,
|
|
|
|
|
level, key_prefix, description, sort_order, is_active
|
|
|
|
|
FROM multi_lang_category
|
|
|
|
|
WHERE is_active = 'Y'
|
|
|
|
|
ORDER BY level ASC, sort_order ASC, category_name ASC`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 트리 구조로 변환
|
|
|
|
|
const categoryMap = new Map<number, LangCategory>();
|
|
|
|
|
const rootCategories: LangCategory[] = [];
|
|
|
|
|
|
|
|
|
|
// 모든 카테고리를 맵에 저장
|
|
|
|
|
categories.forEach((cat) => {
|
|
|
|
|
const category: LangCategory = {
|
|
|
|
|
categoryId: cat.category_id,
|
|
|
|
|
categoryCode: cat.category_code,
|
|
|
|
|
categoryName: cat.category_name,
|
|
|
|
|
parentId: cat.parent_id,
|
|
|
|
|
level: cat.level,
|
|
|
|
|
keyPrefix: cat.key_prefix,
|
|
|
|
|
description: cat.description || undefined,
|
|
|
|
|
sortOrder: cat.sort_order,
|
|
|
|
|
isActive: cat.is_active,
|
|
|
|
|
children: [],
|
|
|
|
|
};
|
|
|
|
|
categoryMap.set(cat.category_id, category);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 부모-자식 관계 설정
|
|
|
|
|
categoryMap.forEach((category) => {
|
|
|
|
|
if (category.parentId && categoryMap.has(category.parentId)) {
|
|
|
|
|
const parent = categoryMap.get(category.parentId)!;
|
|
|
|
|
parent.children = parent.children || [];
|
|
|
|
|
parent.children.push(category);
|
|
|
|
|
} else if (!category.parentId) {
|
|
|
|
|
rootCategories.push(category);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
logger.info(`카테고리 목록 조회 완료: ${categories.length}개`);
|
|
|
|
|
return rootCategories;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("카테고리 목록 조회 중 오류 발생:", error);
|
|
|
|
|
throw new Error(
|
|
|
|
|
`카테고리 목록 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 카테고리 단일 조회
|
|
|
|
|
*/
|
|
|
|
|
async getCategoryById(categoryId: number): Promise<LangCategory | null> {
|
|
|
|
|
try {
|
|
|
|
|
const category = await queryOne<{
|
|
|
|
|
category_id: number;
|
|
|
|
|
category_code: string;
|
|
|
|
|
category_name: string;
|
|
|
|
|
parent_id: number | null;
|
|
|
|
|
level: number;
|
|
|
|
|
key_prefix: string;
|
|
|
|
|
description: string | null;
|
|
|
|
|
sort_order: number;
|
|
|
|
|
is_active: string;
|
|
|
|
|
}>(
|
|
|
|
|
`SELECT category_id, category_code, category_name, parent_id,
|
|
|
|
|
level, key_prefix, description, sort_order, is_active
|
|
|
|
|
FROM multi_lang_category
|
|
|
|
|
WHERE category_id = $1`,
|
|
|
|
|
[categoryId]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!category) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
categoryId: category.category_id,
|
|
|
|
|
categoryCode: category.category_code,
|
|
|
|
|
categoryName: category.category_name,
|
|
|
|
|
parentId: category.parent_id,
|
|
|
|
|
level: category.level,
|
|
|
|
|
keyPrefix: category.key_prefix,
|
|
|
|
|
description: category.description || undefined,
|
|
|
|
|
sortOrder: category.sort_order,
|
|
|
|
|
isActive: category.is_active,
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("카테고리 조회 중 오류 발생:", error);
|
|
|
|
|
throw new Error(
|
|
|
|
|
`카테고리 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 카테고리 경로 조회 (부모 포함)
|
|
|
|
|
*/
|
|
|
|
|
async getCategoryPath(categoryId: number): Promise<LangCategory[]> {
|
|
|
|
|
try {
|
|
|
|
|
const categories = await query<{
|
|
|
|
|
category_id: number;
|
|
|
|
|
category_code: string;
|
|
|
|
|
category_name: string;
|
|
|
|
|
parent_id: number | null;
|
|
|
|
|
level: number;
|
|
|
|
|
key_prefix: string;
|
|
|
|
|
description: string | null;
|
|
|
|
|
sort_order: number;
|
|
|
|
|
is_active: string;
|
|
|
|
|
}>(
|
|
|
|
|
`WITH RECURSIVE category_path AS (
|
|
|
|
|
SELECT category_id, category_code, category_name, parent_id,
|
|
|
|
|
level, key_prefix, description, sort_order, is_active
|
|
|
|
|
FROM multi_lang_category
|
|
|
|
|
WHERE category_id = $1
|
|
|
|
|
UNION ALL
|
|
|
|
|
SELECT c.category_id, c.category_code, c.category_name, c.parent_id,
|
|
|
|
|
c.level, c.key_prefix, c.description, c.sort_order, c.is_active
|
|
|
|
|
FROM multi_lang_category c
|
|
|
|
|
INNER JOIN category_path cp ON c.category_id = cp.parent_id
|
|
|
|
|
)
|
|
|
|
|
SELECT * FROM category_path ORDER BY level ASC`,
|
|
|
|
|
[categoryId]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return categories.map((cat) => ({
|
|
|
|
|
categoryId: cat.category_id,
|
|
|
|
|
categoryCode: cat.category_code,
|
|
|
|
|
categoryName: cat.category_name,
|
|
|
|
|
parentId: cat.parent_id,
|
|
|
|
|
level: cat.level,
|
|
|
|
|
keyPrefix: cat.key_prefix,
|
|
|
|
|
description: cat.description || undefined,
|
|
|
|
|
sortOrder: cat.sort_order,
|
|
|
|
|
isActive: cat.is_active,
|
|
|
|
|
}));
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("카테고리 경로 조회 중 오류 발생:", error);
|
|
|
|
|
throw new Error(
|
|
|
|
|
`카테고리 경로 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 키 자동 생성
|
|
|
|
|
*/
|
|
|
|
|
async generateKey(params: GenerateKeyRequest): Promise<number> {
|
|
|
|
|
try {
|
|
|
|
|
logger.info("키 자동 생성 시작", { params });
|
|
|
|
|
|
|
|
|
|
// 카테고리 경로 조회
|
|
|
|
|
const categoryPath = await this.getCategoryPath(params.categoryId);
|
|
|
|
|
if (categoryPath.length === 0) {
|
|
|
|
|
throw new Error("존재하지 않는 카테고리입니다");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// lang_key 자동 생성 (prefix.meaning 형식)
|
|
|
|
|
const prefixes = categoryPath.map((c) => c.keyPrefix);
|
|
|
|
|
const langKey = [...prefixes, params.keyMeaning].join(".");
|
|
|
|
|
|
|
|
|
|
// 중복 체크
|
|
|
|
|
const existingKey = await queryOne<{ key_id: number }>(
|
|
|
|
|
`SELECT key_id FROM multi_lang_key_master
|
|
|
|
|
WHERE company_code = $1 AND lang_key = $2`,
|
|
|
|
|
[params.companyCode, langKey]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (existingKey) {
|
|
|
|
|
throw new Error(`이미 존재하는 키입니다: ${langKey}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 트랜잭션으로 키와 텍스트 생성
|
|
|
|
|
let keyId: number = 0;
|
|
|
|
|
|
|
|
|
|
await transaction(async (client) => {
|
|
|
|
|
// 키 생성
|
|
|
|
|
const keyResult = await client.query(
|
|
|
|
|
`INSERT INTO multi_lang_key_master
|
|
|
|
|
(company_code, lang_key, category_id, key_meaning, usage_note, description, is_active, created_by, updated_by)
|
|
|
|
|
VALUES ($1, $2, $3, $4, $5, $6, 'Y', $7, $7)
|
|
|
|
|
RETURNING key_id`,
|
|
|
|
|
[
|
|
|
|
|
params.companyCode,
|
|
|
|
|
langKey,
|
|
|
|
|
params.categoryId,
|
|
|
|
|
params.keyMeaning,
|
|
|
|
|
params.usageNote || null,
|
|
|
|
|
params.usageNote || null,
|
|
|
|
|
params.createdBy || "system",
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
keyId = keyResult.rows[0].key_id;
|
|
|
|
|
|
|
|
|
|
// 텍스트 생성
|
|
|
|
|
for (const text of params.texts) {
|
|
|
|
|
await client.query(
|
|
|
|
|
`INSERT INTO multi_lang_text
|
|
|
|
|
(key_id, lang_code, lang_text, is_active, created_by, updated_by)
|
|
|
|
|
VALUES ($1, $2, $3, 'Y', $4, $4)`,
|
|
|
|
|
[keyId, text.langCode, text.langText, params.createdBy || "system"]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
logger.info("키 자동 생성 완료", { keyId, langKey });
|
|
|
|
|
return keyId;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("키 자동 생성 중 오류 발생:", error);
|
|
|
|
|
throw new Error(
|
|
|
|
|
`키 자동 생성 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 회사별 오버라이드 키 생성
|
|
|
|
|
*/
|
|
|
|
|
async createOverrideKey(params: CreateOverrideKeyRequest): Promise<number> {
|
|
|
|
|
try {
|
|
|
|
|
logger.info("오버라이드 키 생성 시작", { params });
|
|
|
|
|
|
|
|
|
|
// 원본 키 조회
|
|
|
|
|
const baseKey = await queryOne<{
|
|
|
|
|
key_id: number;
|
|
|
|
|
company_code: string;
|
|
|
|
|
lang_key: string;
|
|
|
|
|
category_id: number | null;
|
|
|
|
|
key_meaning: string | null;
|
|
|
|
|
}>(
|
|
|
|
|
`SELECT key_id, company_code, lang_key, category_id, key_meaning
|
|
|
|
|
FROM multi_lang_key_master WHERE key_id = $1`,
|
|
|
|
|
[params.baseKeyId]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!baseKey) {
|
|
|
|
|
throw new Error("원본 키를 찾을 수 없습니다");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 공통 키(*)만 오버라이드 가능
|
|
|
|
|
if (baseKey.company_code !== "*") {
|
|
|
|
|
throw new Error("공통 키(*)만 오버라이드 할 수 있습니다");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 이미 오버라이드 키가 있는지 확인
|
|
|
|
|
const existingOverride = await queryOne<{ key_id: number }>(
|
|
|
|
|
`SELECT key_id FROM multi_lang_key_master
|
|
|
|
|
WHERE company_code = $1 AND lang_key = $2`,
|
|
|
|
|
[params.companyCode, baseKey.lang_key]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (existingOverride) {
|
|
|
|
|
throw new Error("이미 해당 회사의 오버라이드 키가 존재합니다");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let keyId: number = 0;
|
|
|
|
|
|
|
|
|
|
await transaction(async (client) => {
|
|
|
|
|
// 오버라이드 키 생성
|
|
|
|
|
const keyResult = await client.query(
|
|
|
|
|
`INSERT INTO multi_lang_key_master
|
|
|
|
|
(company_code, lang_key, category_id, key_meaning, base_key_id, is_active, created_by, updated_by)
|
|
|
|
|
VALUES ($1, $2, $3, $4, $5, 'Y', $6, $6)
|
|
|
|
|
RETURNING key_id`,
|
|
|
|
|
[
|
|
|
|
|
params.companyCode,
|
|
|
|
|
baseKey.lang_key,
|
|
|
|
|
baseKey.category_id,
|
|
|
|
|
baseKey.key_meaning,
|
|
|
|
|
params.baseKeyId,
|
|
|
|
|
params.createdBy || "system",
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
keyId = keyResult.rows[0].key_id;
|
|
|
|
|
|
|
|
|
|
// 텍스트 생성
|
|
|
|
|
for (const text of params.texts) {
|
|
|
|
|
await client.query(
|
|
|
|
|
`INSERT INTO multi_lang_text
|
|
|
|
|
(key_id, lang_code, lang_text, is_active, created_by, updated_by)
|
|
|
|
|
VALUES ($1, $2, $3, 'Y', $4, $4)`,
|
|
|
|
|
[keyId, text.langCode, text.langText, params.createdBy || "system"]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
logger.info("오버라이드 키 생성 완료", { keyId, langKey: baseKey.lang_key });
|
|
|
|
|
return keyId;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("오버라이드 키 생성 중 오류 발생:", error);
|
|
|
|
|
throw new Error(
|
|
|
|
|
`오버라이드 키 생성 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 회사의 오버라이드 키 목록 조회
|
|
|
|
|
*/
|
|
|
|
|
async getOverrideKeys(companyCode: string): Promise<LangKey[]> {
|
|
|
|
|
try {
|
|
|
|
|
logger.info("오버라이드 키 목록 조회 시작", { companyCode });
|
|
|
|
|
|
|
|
|
|
const keys = await query<{
|
|
|
|
|
key_id: number;
|
|
|
|
|
company_code: string;
|
|
|
|
|
lang_key: string;
|
|
|
|
|
category_id: number | null;
|
|
|
|
|
key_meaning: string | null;
|
|
|
|
|
usage_note: string | null;
|
|
|
|
|
base_key_id: number | null;
|
|
|
|
|
is_active: string;
|
|
|
|
|
created_date: Date | null;
|
|
|
|
|
}>(
|
|
|
|
|
`SELECT key_id, company_code, lang_key, category_id, key_meaning,
|
|
|
|
|
usage_note, base_key_id, is_active, created_date
|
|
|
|
|
FROM multi_lang_key_master
|
|
|
|
|
WHERE company_code = $1 AND base_key_id IS NOT NULL
|
|
|
|
|
ORDER BY lang_key ASC`,
|
|
|
|
|
[companyCode]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return keys.map((k) => ({
|
|
|
|
|
keyId: k.key_id,
|
|
|
|
|
companyCode: k.company_code,
|
|
|
|
|
langKey: k.lang_key,
|
|
|
|
|
categoryId: k.category_id ?? undefined,
|
|
|
|
|
keyMeaning: k.key_meaning ?? undefined,
|
|
|
|
|
usageNote: k.usage_note ?? undefined,
|
|
|
|
|
baseKeyId: k.base_key_id ?? undefined,
|
|
|
|
|
isActive: k.is_active,
|
|
|
|
|
createdDate: k.created_date ?? undefined,
|
|
|
|
|
}));
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("오버라이드 키 목록 조회 중 오류 발생:", error);
|
|
|
|
|
throw new Error(
|
|
|
|
|
`오버라이드 키 목록 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 키 존재 여부 및 미리보기 확인
|
|
|
|
|
*/
|
|
|
|
|
async previewGeneratedKey(categoryId: number, keyMeaning: string, companyCode: string): Promise<{
|
|
|
|
|
langKey: string;
|
|
|
|
|
exists: boolean;
|
|
|
|
|
isOverride: boolean;
|
|
|
|
|
baseKeyId?: number;
|
|
|
|
|
}> {
|
|
|
|
|
try {
|
|
|
|
|
// 카테고리 경로 조회
|
|
|
|
|
const categoryPath = await this.getCategoryPath(categoryId);
|
|
|
|
|
if (categoryPath.length === 0) {
|
|
|
|
|
throw new Error("존재하지 않는 카테고리입니다");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// lang_key 생성
|
|
|
|
|
const prefixes = categoryPath.map((c) => c.keyPrefix);
|
|
|
|
|
const langKey = [...prefixes, keyMeaning].join(".");
|
|
|
|
|
|
|
|
|
|
// 공통 키 확인
|
|
|
|
|
const commonKey = await queryOne<{ key_id: number }>(
|
|
|
|
|
`SELECT key_id FROM multi_lang_key_master
|
|
|
|
|
WHERE company_code = '*' AND lang_key = $1`,
|
|
|
|
|
[langKey]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 회사별 키 확인
|
|
|
|
|
const companyKey = await queryOne<{ key_id: number }>(
|
|
|
|
|
`SELECT key_id FROM multi_lang_key_master
|
|
|
|
|
WHERE company_code = $1 AND lang_key = $2`,
|
|
|
|
|
[companyCode, langKey]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
langKey,
|
|
|
|
|
exists: !!companyKey,
|
|
|
|
|
isOverride: !!commonKey && !companyKey,
|
|
|
|
|
baseKeyId: commonKey?.key_id,
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("키 미리보기 중 오류 발생:", error);
|
|
|
|
|
throw new Error(
|
|
|
|
|
`키 미리보기 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-25 15:12:31 +09:00
|
|
|
/**
|
|
|
|
|
* 언어 목록 조회
|
|
|
|
|
*/
|
|
|
|
|
async getLanguages(): Promise<Language[]> {
|
|
|
|
|
try {
|
|
|
|
|
logger.info("언어 목록 조회 시작");
|
|
|
|
|
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
const languages = await query<{
|
|
|
|
|
lang_code: string;
|
|
|
|
|
lang_name: string;
|
|
|
|
|
lang_native: string | null;
|
|
|
|
|
is_active: string | null;
|
|
|
|
|
sort_order: number | null;
|
|
|
|
|
created_date: Date | null;
|
|
|
|
|
created_by: string | null;
|
|
|
|
|
updated_date: Date | null;
|
|
|
|
|
updated_by: string | null;
|
|
|
|
|
}>(
|
|
|
|
|
`SELECT lang_code, lang_name, lang_native, is_active, sort_order,
|
|
|
|
|
created_date, created_by, updated_date, updated_by
|
|
|
|
|
FROM language_master
|
|
|
|
|
ORDER BY sort_order ASC, lang_code ASC`
|
|
|
|
|
);
|
2025-09-01 11:00:38 +09:00
|
|
|
|
|
|
|
|
const mappedLanguages: Language[] = languages.map((lang) => ({
|
|
|
|
|
langCode: lang.lang_code,
|
|
|
|
|
langName: lang.lang_name,
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
langNative: lang.lang_native || "",
|
2025-09-01 11:00:38 +09:00
|
|
|
isActive: lang.is_active || "N",
|
|
|
|
|
sortOrder: lang.sort_order ?? undefined,
|
|
|
|
|
createdDate: lang.created_date || undefined,
|
|
|
|
|
createdBy: lang.created_by || undefined,
|
|
|
|
|
updatedDate: lang.updated_date || undefined,
|
|
|
|
|
updatedBy: lang.updated_by || undefined,
|
2025-08-25 15:12:31 +09:00
|
|
|
}));
|
|
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
logger.info(`언어 목록 조회 완료: ${mappedLanguages.length}개`);
|
|
|
|
|
return mappedLanguages;
|
2025-08-25 15:12:31 +09:00
|
|
|
} 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 });
|
|
|
|
|
|
|
|
|
|
// 중복 체크
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
const existingLanguage = await queryOne<{ lang_code: string }>(
|
|
|
|
|
`SELECT lang_code FROM language_master WHERE lang_code = $1`,
|
|
|
|
|
[languageData.langCode]
|
|
|
|
|
);
|
2025-09-01 11:00:38 +09:00
|
|
|
|
|
|
|
|
if (existingLanguage) {
|
2025-08-25 15:12:31 +09:00
|
|
|
throw new Error(
|
|
|
|
|
`이미 존재하는 언어 코드입니다: ${languageData.langCode}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 언어 생성
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
const createdLanguage = await queryOne<{
|
|
|
|
|
lang_code: string;
|
|
|
|
|
lang_name: string;
|
|
|
|
|
lang_native: string | null;
|
|
|
|
|
is_active: string | null;
|
|
|
|
|
sort_order: number | null;
|
|
|
|
|
created_date: Date | null;
|
|
|
|
|
created_by: string | null;
|
|
|
|
|
updated_date: Date | null;
|
|
|
|
|
updated_by: string | null;
|
|
|
|
|
}>(
|
|
|
|
|
`INSERT INTO language_master
|
|
|
|
|
(lang_code, lang_name, lang_native, is_active, sort_order, created_by, updated_by)
|
|
|
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
|
|
|
RETURNING *`,
|
|
|
|
|
[
|
|
|
|
|
languageData.langCode,
|
|
|
|
|
languageData.langName,
|
|
|
|
|
languageData.langNative,
|
|
|
|
|
languageData.isActive || "Y",
|
|
|
|
|
languageData.sortOrder || 0,
|
|
|
|
|
languageData.createdBy || "system",
|
|
|
|
|
languageData.updatedBy || "system",
|
|
|
|
|
]
|
|
|
|
|
);
|
2025-08-25 15:12:31 +09:00
|
|
|
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
logger.info("언어 생성 완료", { langCode: createdLanguage!.lang_code });
|
2025-08-25 15:12:31 +09:00
|
|
|
|
|
|
|
|
return {
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
langCode: createdLanguage!.lang_code,
|
|
|
|
|
langName: createdLanguage!.lang_name,
|
|
|
|
|
langNative: createdLanguage!.lang_native || "",
|
|
|
|
|
isActive: createdLanguage!.is_active || "N",
|
|
|
|
|
sortOrder: createdLanguage!.sort_order ?? undefined,
|
|
|
|
|
createdDate: createdLanguage!.created_date || undefined,
|
|
|
|
|
createdBy: createdLanguage!.created_by || undefined,
|
|
|
|
|
updatedDate: createdLanguage!.updated_date || undefined,
|
|
|
|
|
updatedBy: createdLanguage!.updated_by || undefined,
|
2025-08-25 15:12:31 +09:00
|
|
|
};
|
|
|
|
|
} 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 });
|
|
|
|
|
|
|
|
|
|
// 기존 언어 확인
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
const existingLanguage = await queryOne<{ lang_code: string }>(
|
|
|
|
|
`SELECT lang_code FROM language_master WHERE lang_code = $1`,
|
|
|
|
|
[langCode]
|
|
|
|
|
);
|
2025-08-25 15:12:31 +09:00
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
if (!existingLanguage) {
|
2025-08-25 15:12:31 +09:00
|
|
|
throw new Error(`언어를 찾을 수 없습니다: ${langCode}`);
|
|
|
|
|
}
|
|
|
|
|
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
// 동적 UPDATE 쿼리 생성
|
|
|
|
|
const updates: string[] = [];
|
|
|
|
|
const values: any[] = [];
|
|
|
|
|
let paramIndex = 1;
|
|
|
|
|
|
|
|
|
|
if (languageData.langName) {
|
|
|
|
|
updates.push(`lang_name = $${paramIndex++}`);
|
|
|
|
|
values.push(languageData.langName);
|
|
|
|
|
}
|
|
|
|
|
if (languageData.langNative) {
|
|
|
|
|
updates.push(`lang_native = $${paramIndex++}`);
|
|
|
|
|
values.push(languageData.langNative);
|
|
|
|
|
}
|
|
|
|
|
if (languageData.isActive) {
|
|
|
|
|
updates.push(`is_active = $${paramIndex++}`);
|
|
|
|
|
values.push(languageData.isActive);
|
|
|
|
|
}
|
|
|
|
|
if (languageData.sortOrder !== undefined) {
|
|
|
|
|
updates.push(`sort_order = $${paramIndex++}`);
|
|
|
|
|
values.push(languageData.sortOrder);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updates.push(`updated_by = $${paramIndex++}`);
|
|
|
|
|
values.push(languageData.updatedBy || "system");
|
|
|
|
|
|
|
|
|
|
values.push(langCode); // WHERE 조건용
|
|
|
|
|
|
2025-08-25 15:12:31 +09:00
|
|
|
// 언어 수정
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
const updatedLanguage = await queryOne<{
|
|
|
|
|
lang_code: string;
|
|
|
|
|
lang_name: string;
|
|
|
|
|
lang_native: string | null;
|
|
|
|
|
is_active: string | null;
|
|
|
|
|
sort_order: number | null;
|
|
|
|
|
created_date: Date | null;
|
|
|
|
|
created_by: string | null;
|
|
|
|
|
updated_date: Date | null;
|
|
|
|
|
updated_by: string | null;
|
|
|
|
|
}>(
|
|
|
|
|
`UPDATE language_master SET ${updates.join(", ")}
|
|
|
|
|
WHERE lang_code = $${paramIndex}
|
|
|
|
|
RETURNING *`,
|
|
|
|
|
values
|
|
|
|
|
);
|
2025-08-25 15:12:31 +09:00
|
|
|
|
|
|
|
|
logger.info("언어 수정 완료", { langCode });
|
|
|
|
|
|
|
|
|
|
return {
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
langCode: updatedLanguage!.lang_code,
|
|
|
|
|
langName: updatedLanguage!.lang_name,
|
|
|
|
|
langNative: updatedLanguage!.lang_native || "",
|
|
|
|
|
isActive: updatedLanguage!.is_active || "N",
|
|
|
|
|
sortOrder: updatedLanguage!.sort_order ?? undefined,
|
|
|
|
|
createdDate: updatedLanguage!.created_date || undefined,
|
|
|
|
|
createdBy: updatedLanguage!.created_by || undefined,
|
|
|
|
|
updatedDate: updatedLanguage!.updated_date || undefined,
|
|
|
|
|
updatedBy: updatedLanguage!.updated_by || undefined,
|
2025-08-25 15:12:31 +09:00
|
|
|
};
|
|
|
|
|
} 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 });
|
|
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
// 현재 언어 조회
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
const currentLanguage = await queryOne<{ is_active: string | null }>(
|
|
|
|
|
`SELECT is_active FROM language_master WHERE lang_code = $1`,
|
|
|
|
|
[langCode]
|
|
|
|
|
);
|
2025-08-25 15:12:31 +09:00
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
if (!currentLanguage) {
|
2025-08-25 15:12:31 +09:00
|
|
|
throw new Error(`언어를 찾을 수 없습니다: ${langCode}`);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
const newStatus = currentLanguage.is_active === "Y" ? "N" : "Y";
|
2025-08-25 15:12:31 +09:00
|
|
|
|
|
|
|
|
// 상태 업데이트
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
await query(
|
|
|
|
|
`UPDATE language_master
|
|
|
|
|
SET is_active = $1, updated_by = $2
|
|
|
|
|
WHERE lang_code = $3`,
|
|
|
|
|
[newStatus, "system", langCode]
|
|
|
|
|
);
|
2025-08-25 15:12:31 +09:00
|
|
|
|
|
|
|
|
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 });
|
|
|
|
|
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
const whereConditions: string[] = [];
|
|
|
|
|
const values: any[] = [];
|
|
|
|
|
let paramIndex = 1;
|
2025-08-25 15:12:31 +09:00
|
|
|
|
|
|
|
|
// 회사 코드 필터
|
|
|
|
|
if (params.companyCode) {
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
whereConditions.push(`company_code = $${paramIndex++}`);
|
|
|
|
|
values.push(params.companyCode);
|
2025-08-25 15:12:31 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 메뉴 코드 필터
|
|
|
|
|
if (params.menuCode) {
|
2026-01-13 18:28:11 +09:00
|
|
|
whereConditions.push(`usage_note = $${paramIndex++}`);
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
values.push(params.menuCode);
|
2025-08-25 15:12:31 +09:00
|
|
|
}
|
|
|
|
|
|
2026-01-14 11:05:57 +09:00
|
|
|
// 카테고리 필터 (하위 카테고리 포함)
|
|
|
|
|
if (params.categoryId) {
|
|
|
|
|
whereConditions.push(`category_id IN (
|
|
|
|
|
WITH RECURSIVE category_tree AS (
|
|
|
|
|
SELECT category_id FROM multi_lang_category WHERE category_id = $${paramIndex}
|
|
|
|
|
UNION ALL
|
|
|
|
|
SELECT c.category_id FROM multi_lang_category c
|
|
|
|
|
INNER JOIN category_tree ct ON c.parent_id = ct.category_id
|
|
|
|
|
)
|
|
|
|
|
SELECT category_id FROM category_tree
|
|
|
|
|
)`);
|
|
|
|
|
values.push(params.categoryId);
|
|
|
|
|
paramIndex++;
|
|
|
|
|
}
|
|
|
|
|
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
// 검색 조건 (OR)
|
2025-08-25 15:12:31 +09:00
|
|
|
if (params.searchText) {
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
whereConditions.push(
|
2026-01-13 18:28:11 +09:00
|
|
|
`(lang_key ILIKE $${paramIndex} OR description ILIKE $${paramIndex} OR usage_note ILIKE $${paramIndex})`
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
);
|
|
|
|
|
values.push(`%${params.searchText}%`);
|
|
|
|
|
paramIndex++;
|
2025-08-25 15:12:31 +09:00
|
|
|
}
|
|
|
|
|
|
2025-10-01 10:27:15 +09:00
|
|
|
const whereClause =
|
|
|
|
|
whereConditions.length > 0
|
|
|
|
|
? `WHERE ${whereConditions.join(" AND ")}`
|
|
|
|
|
: "";
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
|
|
|
|
|
const langKeys = await query<{
|
|
|
|
|
key_id: number;
|
|
|
|
|
company_code: string;
|
2026-01-13 18:28:11 +09:00
|
|
|
usage_note: string | null;
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
lang_key: string;
|
|
|
|
|
description: string | null;
|
|
|
|
|
is_active: string | null;
|
2026-01-14 11:05:57 +09:00
|
|
|
category_id: number | null;
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
created_date: Date | null;
|
|
|
|
|
created_by: string | null;
|
|
|
|
|
updated_date: Date | null;
|
|
|
|
|
updated_by: string | null;
|
|
|
|
|
}>(
|
2026-01-14 11:05:57 +09:00
|
|
|
`SELECT key_id, company_code, usage_note, lang_key, description, is_active, category_id,
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
created_date, created_by, updated_date, updated_by
|
|
|
|
|
FROM multi_lang_key_master
|
|
|
|
|
${whereClause}
|
2026-01-13 18:28:11 +09:00
|
|
|
ORDER BY company_code ASC, usage_note ASC, lang_key ASC`,
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
values
|
|
|
|
|
);
|
2025-08-25 15:12:31 +09:00
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
const mappedKeys: LangKey[] = langKeys.map((key) => ({
|
|
|
|
|
keyId: key.key_id,
|
|
|
|
|
companyCode: key.company_code,
|
2026-01-13 18:28:11 +09:00
|
|
|
menuName: key.usage_note || undefined,
|
2025-09-01 11:00:38 +09:00
|
|
|
langKey: key.lang_key,
|
|
|
|
|
description: key.description || undefined,
|
|
|
|
|
isActive: key.is_active || "Y",
|
2026-01-14 11:05:57 +09:00
|
|
|
categoryId: key.category_id || undefined,
|
2025-09-01 11:00:38 +09:00
|
|
|
createdDate: key.created_date || undefined,
|
|
|
|
|
createdBy: key.created_by || undefined,
|
|
|
|
|
updatedDate: key.updated_date || undefined,
|
|
|
|
|
updatedBy: key.updated_by || undefined,
|
2025-08-25 15:12:31 +09:00
|
|
|
}));
|
|
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
logger.info(`다국어 키 목록 조회 완료: ${mappedKeys.length}개`);
|
|
|
|
|
return mappedKeys;
|
2025-08-25 15:12:31 +09:00
|
|
|
} 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 });
|
|
|
|
|
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
const langTexts = await query<{
|
|
|
|
|
text_id: number;
|
|
|
|
|
key_id: number;
|
|
|
|
|
lang_code: string;
|
|
|
|
|
lang_text: string;
|
|
|
|
|
is_active: string | null;
|
|
|
|
|
created_date: Date | null;
|
|
|
|
|
created_by: string | null;
|
|
|
|
|
updated_date: Date | null;
|
|
|
|
|
updated_by: string | null;
|
|
|
|
|
}>(
|
|
|
|
|
`SELECT text_id, key_id, lang_code, lang_text, is_active,
|
|
|
|
|
created_date, created_by, updated_date, updated_by
|
|
|
|
|
FROM multi_lang_text
|
|
|
|
|
WHERE key_id = $1 AND is_active = $2
|
|
|
|
|
ORDER BY lang_code ASC`,
|
|
|
|
|
[keyId, "Y"]
|
|
|
|
|
);
|
2025-09-01 11:00:38 +09:00
|
|
|
|
|
|
|
|
const mappedTexts: LangText[] = langTexts.map((text) => ({
|
|
|
|
|
textId: text.text_id,
|
|
|
|
|
keyId: text.key_id,
|
|
|
|
|
langCode: text.lang_code,
|
|
|
|
|
langText: text.lang_text,
|
|
|
|
|
isActive: text.is_active || "Y",
|
|
|
|
|
createdDate: text.created_date || undefined,
|
|
|
|
|
createdBy: text.created_by || undefined,
|
|
|
|
|
updatedDate: text.updated_date || undefined,
|
|
|
|
|
updatedBy: text.updated_by || undefined,
|
2025-08-25 15:12:31 +09:00
|
|
|
}));
|
|
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
logger.info(`다국어 텍스트 조회 완료: ${mappedTexts.length}개`);
|
|
|
|
|
return mappedTexts;
|
2025-08-25 15:12:31 +09:00
|
|
|
} 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 });
|
|
|
|
|
|
|
|
|
|
// 중복 체크
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
const existingKey = await queryOne<{ key_id: number }>(
|
|
|
|
|
`SELECT key_id FROM multi_lang_key_master
|
|
|
|
|
WHERE company_code = $1 AND lang_key = $2`,
|
|
|
|
|
[keyData.companyCode, keyData.langKey]
|
|
|
|
|
);
|
2025-09-01 11:00:38 +09:00
|
|
|
|
|
|
|
|
if (existingKey) {
|
2025-08-25 15:12:31 +09:00
|
|
|
throw new Error(
|
|
|
|
|
`동일한 회사에 이미 존재하는 언어키입니다: ${keyData.langKey}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 다국어 키 생성
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
const createdKey = await queryOne<{ key_id: number }>(
|
|
|
|
|
`INSERT INTO multi_lang_key_master
|
2026-01-13 18:28:11 +09:00
|
|
|
(company_code, usage_note, lang_key, description, is_active, created_by, updated_by)
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
|
|
|
RETURNING key_id`,
|
|
|
|
|
[
|
|
|
|
|
keyData.companyCode,
|
|
|
|
|
keyData.menuName || null,
|
|
|
|
|
keyData.langKey,
|
|
|
|
|
keyData.description || null,
|
|
|
|
|
keyData.isActive || "Y",
|
|
|
|
|
keyData.createdBy || "system",
|
|
|
|
|
keyData.updatedBy || "system",
|
|
|
|
|
]
|
|
|
|
|
);
|
2025-09-01 11:00:38 +09:00
|
|
|
|
|
|
|
|
logger.info("다국어 키 생성 완료", {
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
keyId: createdKey!.key_id,
|
2025-09-01 11:00:38 +09:00
|
|
|
langKey: keyData.langKey,
|
|
|
|
|
});
|
|
|
|
|
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
return createdKey!.key_id;
|
2025-08-25 15:12:31 +09:00
|
|
|
} 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 });
|
|
|
|
|
|
|
|
|
|
// 기존 키 확인
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
const existingKey = await queryOne<{ key_id: number }>(
|
|
|
|
|
`SELECT key_id FROM multi_lang_key_master WHERE key_id = $1`,
|
|
|
|
|
[keyId]
|
|
|
|
|
);
|
2025-08-25 15:12:31 +09:00
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
if (!existingKey) {
|
2025-08-25 15:12:31 +09:00
|
|
|
throw new Error(`다국어 키를 찾을 수 없습니다: ${keyId}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 중복 체크 (자신을 제외하고)
|
|
|
|
|
if (keyData.companyCode && keyData.langKey) {
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
const duplicateKey = await queryOne<{ key_id: number }>(
|
|
|
|
|
`SELECT key_id FROM multi_lang_key_master
|
|
|
|
|
WHERE company_code = $1 AND lang_key = $2 AND key_id != $3`,
|
|
|
|
|
[keyData.companyCode, keyData.langKey, keyId]
|
|
|
|
|
);
|
2025-09-01 11:00:38 +09:00
|
|
|
|
|
|
|
|
if (duplicateKey) {
|
2025-08-25 15:12:31 +09:00
|
|
|
throw new Error(
|
|
|
|
|
`동일한 회사에 이미 존재하는 언어키입니다: ${keyData.langKey}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
// 동적 UPDATE 쿼리 생성
|
|
|
|
|
const updates: string[] = [];
|
|
|
|
|
const values: any[] = [];
|
|
|
|
|
let paramIndex = 1;
|
|
|
|
|
|
|
|
|
|
if (keyData.companyCode) {
|
|
|
|
|
updates.push(`company_code = $${paramIndex++}`);
|
|
|
|
|
values.push(keyData.companyCode);
|
|
|
|
|
}
|
|
|
|
|
if (keyData.menuName !== undefined) {
|
2026-01-13 18:28:11 +09:00
|
|
|
updates.push(`usage_note = $${paramIndex++}`);
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
values.push(keyData.menuName);
|
|
|
|
|
}
|
|
|
|
|
if (keyData.langKey) {
|
|
|
|
|
updates.push(`lang_key = $${paramIndex++}`);
|
|
|
|
|
values.push(keyData.langKey);
|
|
|
|
|
}
|
|
|
|
|
if (keyData.description !== undefined) {
|
|
|
|
|
updates.push(`description = $${paramIndex++}`);
|
|
|
|
|
values.push(keyData.description);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updates.push(`updated_by = $${paramIndex++}`);
|
|
|
|
|
values.push(keyData.updatedBy || "system");
|
|
|
|
|
|
|
|
|
|
values.push(keyId); // WHERE 조건용
|
|
|
|
|
|
2025-08-25 15:12:31 +09:00
|
|
|
// 다국어 키 수정
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
await query(
|
|
|
|
|
`UPDATE multi_lang_key_master SET ${updates.join(", ")}
|
|
|
|
|
WHERE key_id = $${paramIndex}`,
|
|
|
|
|
values
|
|
|
|
|
);
|
2025-08-25 15:12:31 +09:00
|
|
|
|
|
|
|
|
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 });
|
|
|
|
|
|
|
|
|
|
// 기존 키 확인
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
const existingKey = await queryOne<{ key_id: number }>(
|
|
|
|
|
`SELECT key_id FROM multi_lang_key_master WHERE key_id = $1`,
|
|
|
|
|
[keyId]
|
|
|
|
|
);
|
2025-08-25 15:12:31 +09:00
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
if (!existingKey) {
|
2025-08-25 15:12:31 +09:00
|
|
|
throw new Error(`다국어 키를 찾을 수 없습니다: ${keyId}`);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
// 트랜잭션으로 키와 연관된 텍스트 모두 삭제
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
await transaction(async (client) => {
|
2025-08-25 15:12:31 +09:00
|
|
|
// 관련된 다국어 텍스트 삭제
|
2025-10-01 10:27:15 +09:00
|
|
|
await client.query(`DELETE FROM multi_lang_text WHERE key_id = $1`, [
|
|
|
|
|
keyId,
|
|
|
|
|
]);
|
2025-08-25 15:12:31 +09:00
|
|
|
|
|
|
|
|
// 다국어 키 삭제
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
await client.query(
|
|
|
|
|
`DELETE FROM multi_lang_key_master WHERE key_id = $1`,
|
|
|
|
|
[keyId]
|
|
|
|
|
);
|
2025-09-01 11:00:38 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
logger.info("다국어 키 삭제 완료", { keyId });
|
2025-08-25 15:12:31 +09:00
|
|
|
} 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 });
|
|
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
// 현재 키 조회
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
const currentKey = await queryOne<{ is_active: string | null }>(
|
|
|
|
|
`SELECT is_active FROM multi_lang_key_master WHERE key_id = $1`,
|
|
|
|
|
[keyId]
|
|
|
|
|
);
|
2025-08-25 15:12:31 +09:00
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
if (!currentKey) {
|
2025-08-25 15:12:31 +09:00
|
|
|
throw new Error(`다국어 키를 찾을 수 없습니다: ${keyId}`);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
const newStatus = currentKey.is_active === "Y" ? "N" : "Y";
|
2025-08-25 15:12:31 +09:00
|
|
|
|
|
|
|
|
// 상태 업데이트
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
await query(
|
|
|
|
|
`UPDATE multi_lang_key_master
|
|
|
|
|
SET is_active = $1, updated_by = $2
|
|
|
|
|
WHERE key_id = $3`,
|
|
|
|
|
[newStatus, "system", keyId]
|
|
|
|
|
);
|
2025-08-25 15:12:31 +09:00
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 기존 키 확인
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
const existingKey = await queryOne<{ key_id: number }>(
|
|
|
|
|
`SELECT key_id FROM multi_lang_key_master WHERE key_id = $1`,
|
|
|
|
|
[keyId]
|
|
|
|
|
);
|
2025-08-25 15:12:31 +09:00
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
if (!existingKey) {
|
2025-08-25 15:12:31 +09:00
|
|
|
throw new Error(`다국어 키를 찾을 수 없습니다: ${keyId}`);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
// 트랜잭션으로 기존 텍스트 삭제 후 새로 생성
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
await transaction(async (client) => {
|
2025-08-25 15:12:31 +09:00
|
|
|
// 기존 텍스트 삭제
|
2025-10-01 10:27:15 +09:00
|
|
|
await client.query(`DELETE FROM multi_lang_text WHERE key_id = $1`, [
|
|
|
|
|
keyId,
|
|
|
|
|
]);
|
2025-08-25 15:12:31 +09:00
|
|
|
|
|
|
|
|
// 새로운 텍스트 삽입
|
2025-09-01 11:00:38 +09:00
|
|
|
if (textData.texts.length > 0) {
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
for (const text of textData.texts) {
|
|
|
|
|
await client.query(
|
|
|
|
|
`INSERT INTO multi_lang_text
|
|
|
|
|
(key_id, lang_code, lang_text, is_active, created_by, updated_by)
|
|
|
|
|
VALUES ($1, $2, $3, $4, $5, $6)`,
|
|
|
|
|
[
|
|
|
|
|
keyId,
|
|
|
|
|
text.langCode,
|
|
|
|
|
text.langText,
|
|
|
|
|
text.isActive || "Y",
|
|
|
|
|
text.createdBy || "system",
|
|
|
|
|
text.updatedBy || "system",
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-08-25 15:12:31 +09:00
|
|
|
}
|
2025-09-01 11:00:38 +09:00
|
|
|
});
|
2025-08-25 15:12:31 +09:00
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
logger.info("다국어 텍스트 저장 완료", {
|
|
|
|
|
keyId,
|
|
|
|
|
savedCount: textData.texts.length,
|
|
|
|
|
});
|
2025-08-25 15:12:31 +09:00
|
|
|
} 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 });
|
|
|
|
|
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
const result = await queryOne<{ lang_text: string }>(
|
|
|
|
|
`SELECT mlt.lang_text
|
|
|
|
|
FROM multi_lang_text mlt
|
|
|
|
|
INNER JOIN multi_lang_key_master mlkm ON mlt.key_id = mlkm.key_id
|
|
|
|
|
WHERE mlt.lang_code = $1
|
|
|
|
|
AND mlt.is_active = $2
|
|
|
|
|
AND mlkm.company_code = $3
|
2026-01-13 18:28:11 +09:00
|
|
|
AND mlkm.usage_note = $4
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
AND mlkm.lang_key = $5
|
|
|
|
|
AND mlkm.is_active = $6`,
|
2025-10-01 10:27:15 +09:00
|
|
|
[
|
|
|
|
|
params.userLang,
|
|
|
|
|
"Y",
|
|
|
|
|
params.companyCode,
|
|
|
|
|
params.menuCode,
|
|
|
|
|
params.langKey,
|
|
|
|
|
"Y",
|
|
|
|
|
]
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
);
|
2025-09-01 11:00:38 +09:00
|
|
|
|
|
|
|
|
if (!result) {
|
2025-08-25 15:12:31 +09:00
|
|
|
logger.warn("사용자별 다국어 텍스트를 찾을 수 없음", { params });
|
|
|
|
|
return params.langKey; // 기본값으로 키 반환
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
logger.info("사용자별 다국어 텍스트 조회 완료", {
|
|
|
|
|
params,
|
|
|
|
|
langText: result.lang_text,
|
|
|
|
|
});
|
2025-08-25 15:12:31 +09:00
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
return result.lang_text;
|
2025-08-25 15:12:31 +09:00
|
|
|
} 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,
|
|
|
|
|
});
|
|
|
|
|
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
const result = await queryOne<{ lang_text: string }>(
|
|
|
|
|
`SELECT mlt.lang_text
|
|
|
|
|
FROM multi_lang_text mlt
|
|
|
|
|
INNER JOIN multi_lang_key_master mlkm ON mlt.key_id = mlkm.key_id
|
|
|
|
|
WHERE mlt.lang_code = $1
|
|
|
|
|
AND mlt.is_active = $2
|
|
|
|
|
AND mlkm.company_code = $3
|
|
|
|
|
AND mlkm.lang_key = $4
|
|
|
|
|
AND mlkm.is_active = $5`,
|
|
|
|
|
[langCode, "Y", companyCode, langKey, "Y"]
|
|
|
|
|
);
|
2025-08-25 15:12:31 +09:00
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
if (!result) {
|
2025-08-25 15:12:31 +09:00
|
|
|
logger.warn("특정 키의 다국어 텍스트를 찾을 수 없음", {
|
|
|
|
|
companyCode,
|
|
|
|
|
langKey,
|
|
|
|
|
langCode,
|
|
|
|
|
});
|
|
|
|
|
return langKey; // 기본값으로 키 반환
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.info("특정 키의 다국어 텍스트 조회 완료", {
|
|
|
|
|
companyCode,
|
|
|
|
|
langKey,
|
|
|
|
|
langCode,
|
2025-09-01 11:00:38 +09:00
|
|
|
langText: result.lang_text,
|
2025-08-25 15:12:31 +09:00
|
|
|
});
|
|
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
return result.lang_text;
|
2025-08-25 15:12:31 +09:00
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("특정 키의 다국어 텍스트 조회 중 오류 발생:", error);
|
|
|
|
|
throw new Error(
|
|
|
|
|
`특정 키의 다국어 텍스트 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-01-13 18:28:11 +09:00
|
|
|
* 배치 번역 조회 (회사별 우선순위 적용)
|
|
|
|
|
* 우선순위: 회사별 키 > 공통 키(*)
|
2025-08-25 15:12:31 +09:00
|
|
|
*/
|
|
|
|
|
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 {};
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
// 모든 키에 대한 번역 조회
|
2025-10-01 10:27:15 +09:00
|
|
|
const placeholders = params.langKeys
|
|
|
|
|
.map((_, i) => `$${i + 4}`)
|
|
|
|
|
.join(", ");
|
|
|
|
|
|
2026-01-13 18:28:11 +09:00
|
|
|
// 회사별 우선순위를 적용하기 위해 정렬 수정
|
|
|
|
|
// 회사별 키가 먼저 오도록 DESC 정렬 (company_code가 '*'보다 특정 회사 코드가 알파벳 순으로 앞)
|
|
|
|
|
// 또는 CASE WHEN을 사용하여 명시적으로 우선순위 설정
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
const translations = await query<{
|
|
|
|
|
lang_text: string;
|
|
|
|
|
lang_key: string;
|
|
|
|
|
company_code: string;
|
2026-01-13 18:28:11 +09:00
|
|
|
priority: number;
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
}>(
|
2026-01-13 18:28:11 +09:00
|
|
|
`SELECT mlt.lang_text, mlkm.lang_key, mlkm.company_code,
|
|
|
|
|
CASE WHEN mlkm.company_code = $3 THEN 1 ELSE 2 END as priority
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
FROM multi_lang_text mlt
|
|
|
|
|
INNER JOIN multi_lang_key_master mlkm ON mlt.key_id = mlkm.key_id
|
|
|
|
|
WHERE mlt.lang_code = $1
|
|
|
|
|
AND mlt.is_active = $2
|
|
|
|
|
AND mlkm.lang_key IN (${placeholders})
|
|
|
|
|
AND mlkm.company_code IN ($3, '*')
|
|
|
|
|
AND mlkm.is_active = $2
|
2026-01-13 18:28:11 +09:00
|
|
|
ORDER BY mlkm.lang_key ASC, priority ASC`,
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
[params.userLang, "Y", params.companyCode, ...params.langKeys]
|
|
|
|
|
);
|
2025-08-25 15:12:31 +09:00
|
|
|
|
|
|
|
|
const result: Record<string, string> = {};
|
|
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
// 기본값으로 모든 키 설정
|
|
|
|
|
params.langKeys.forEach((key) => {
|
2025-08-25 15:12:31 +09:00
|
|
|
result[key] = key;
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-13 18:28:11 +09:00
|
|
|
// 우선순위 기반으로 번역 적용
|
|
|
|
|
// priority가 낮은 것(회사별)이 먼저 오므로, 먼저 처리된 키는 덮어쓰지 않음
|
|
|
|
|
const processedKeys = new Set<string>();
|
|
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
translations.forEach((translation) => {
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
const langKey = translation.lang_key;
|
2026-01-13 18:28:11 +09:00
|
|
|
if (params.langKeys.includes(langKey) && !processedKeys.has(langKey)) {
|
2025-09-01 11:00:38 +09:00
|
|
|
result[langKey] = translation.lang_text;
|
2026-01-13 18:28:11 +09:00
|
|
|
processedKeys.add(langKey);
|
2025-09-01 11:00:38 +09:00
|
|
|
}
|
|
|
|
|
});
|
2025-08-25 15:12:31 +09:00
|
|
|
|
|
|
|
|
logger.info("배치 번역 조회 완료", {
|
|
|
|
|
totalKeys: params.langKeys.length,
|
2025-09-01 11:00:38 +09:00
|
|
|
foundTranslations: translations.length,
|
2026-01-13 18:28:11 +09:00
|
|
|
companyOverrides: translations.filter(t => t.company_code !== '*').length,
|
2025-08-25 15:12:31 +09:00
|
|
|
resultKeys: Object.keys(result).length,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("배치 번역 조회 중 오류 발생:", error);
|
|
|
|
|
throw new Error(
|
|
|
|
|
`배치 번역 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-29 10:44:55 +09:00
|
|
|
/**
|
|
|
|
|
* 언어 삭제
|
|
|
|
|
*/
|
|
|
|
|
async deleteLanguage(langCode: string): Promise<void> {
|
|
|
|
|
try {
|
|
|
|
|
logger.info("언어 삭제 시작", { langCode });
|
|
|
|
|
|
|
|
|
|
// 기존 언어 확인
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
const existingLanguage = await queryOne<{ lang_code: string }>(
|
|
|
|
|
`SELECT lang_code FROM language_master WHERE lang_code = $1`,
|
|
|
|
|
[langCode]
|
|
|
|
|
);
|
2025-08-29 10:44:55 +09:00
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
if (!existingLanguage) {
|
2025-08-29 10:44:55 +09:00
|
|
|
throw new Error(`언어를 찾을 수 없습니다: ${langCode}`);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-01 11:00:38 +09:00
|
|
|
// 트랜잭션으로 언어와 관련 텍스트 삭제
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
await transaction(async (client) => {
|
2025-09-01 11:00:38 +09:00
|
|
|
// 해당 언어의 다국어 텍스트 삭제
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
const deleteResult = await client.query(
|
|
|
|
|
`DELETE FROM multi_lang_text WHERE lang_code = $1`,
|
|
|
|
|
[langCode]
|
|
|
|
|
);
|
2025-08-29 10:44:55 +09:00
|
|
|
|
feat: Phase 3.1 MultiLangService Raw Query 전환 완료
25개 Prisma 호출을 모두 Raw Query로 전환
- 언어 관리 (getLanguages, createLanguage, updateLanguage, toggleLanguage, deleteLanguage)
- 다국어 키 관리 (getLangKeys, createLangKey, updateLangKey, deleteLangKey, toggleLangKey)
- 다국어 텍스트 관리 (getLangTexts, saveLangTexts, getUserText, getLangText)
- 배치 번역 조회 (getBatchTranslations)
주요 기술적 해결:
- 동적 WHERE 조건 생성 (ILIKE 검색 지원)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- 트랜잭션 처리 (transaction 함수 사용)
- JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- IN 절 동적 파라미터 바인딩 (배치 번역)
TypeScript 컴파일 성공 (linter 에러 0개)
Prisma import 완전 제거
Phase 3 진행률: 25/162 (15.4%)
전체 진행률: 276/444 (62.2%)
2025-10-01 10:25:38 +09:00
|
|
|
logger.info(`삭제된 다국어 텍스트 수: ${deleteResult.rowCount}`, {
|
2025-08-29 16:54:43 +09:00
|
|
|
langCode,
|
|
|
|
|
});
|
2025-08-29 10:44:55 +09:00
|
|
|
|
|
|
|
|
// 언어 마스터 삭제
|
2025-10-01 10:27:15 +09:00
|
|
|
await client.query(`DELETE FROM language_master WHERE lang_code = $1`, [
|
|
|
|
|
langCode,
|
|
|
|
|
]);
|
2025-09-01 11:00:38 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
logger.info("언어 삭제 완료", { langCode });
|
2025-08-29 10:44:55 +09:00
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("언어 삭제 중 오류 발생:", error);
|
|
|
|
|
throw new Error(
|
|
|
|
|
`언어 삭제 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-14 10:20:27 +09:00
|
|
|
|
|
|
|
|
// =====================================================
|
|
|
|
|
// 회사/메뉴 기반 카테고리 자동 생성 메서드
|
|
|
|
|
// =====================================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 화면(screen) 루트 카테고리 확인 또는 생성
|
|
|
|
|
*/
|
|
|
|
|
async ensureScreenRootCategory(): Promise<number> {
|
|
|
|
|
try {
|
|
|
|
|
// 기존 screen 카테고리 확인
|
|
|
|
|
const existing = await queryOne<{ category_id: number }>(
|
|
|
|
|
`SELECT category_id FROM multi_lang_category
|
|
|
|
|
WHERE category_code = 'screen' AND parent_id IS NULL`,
|
|
|
|
|
[]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (existing) {
|
|
|
|
|
return existing.category_id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 없으면 생성
|
|
|
|
|
const result = await queryOne<{ category_id: number }>(
|
|
|
|
|
`INSERT INTO multi_lang_category
|
|
|
|
|
(category_code, category_name, parent_id, level, key_prefix, description, sort_order, is_active, created_date)
|
|
|
|
|
VALUES ('screen', '화면', NULL, 1, 'screen', '화면 디자이너에서 자동 생성된 다국어 키', 100, 'Y', NOW())
|
|
|
|
|
RETURNING category_id`,
|
|
|
|
|
[]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
logger.info("화면 루트 카테고리 생성", { categoryId: result?.category_id });
|
|
|
|
|
return result!.category_id;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("화면 루트 카테고리 생성 실패:", error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 회사 카테고리 확인 또는 생성
|
|
|
|
|
*/
|
|
|
|
|
async ensureCompanyCategory(companyCode: string, companyName: string): Promise<number> {
|
|
|
|
|
try {
|
|
|
|
|
const screenRootId = await this.ensureScreenRootCategory();
|
|
|
|
|
|
|
|
|
|
// 기존 회사 카테고리 확인
|
|
|
|
|
const existing = await queryOne<{ category_id: number }>(
|
|
|
|
|
`SELECT category_id FROM multi_lang_category
|
|
|
|
|
WHERE category_code = $1 AND parent_id = $2`,
|
|
|
|
|
[companyCode, screenRootId]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (existing) {
|
|
|
|
|
return existing.category_id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 회사 카테고리 생성
|
|
|
|
|
const displayName = companyCode === "*" ? "공통" : companyName;
|
|
|
|
|
const keyPrefix = companyCode === "*" ? "common" : companyCode.toLowerCase();
|
|
|
|
|
|
|
|
|
|
const result = await queryOne<{ category_id: number }>(
|
|
|
|
|
`INSERT INTO multi_lang_category
|
|
|
|
|
(category_code, category_name, parent_id, level, key_prefix, description, sort_order, is_active, created_date)
|
|
|
|
|
VALUES ($1, $2, $3, 2, $4, $5, $6, 'Y', NOW())
|
|
|
|
|
RETURNING category_id`,
|
|
|
|
|
[
|
|
|
|
|
companyCode,
|
|
|
|
|
displayName,
|
|
|
|
|
screenRootId,
|
|
|
|
|
keyPrefix,
|
|
|
|
|
`${displayName} 회사의 화면 다국어`,
|
|
|
|
|
companyCode === "*" ? 0 : 10,
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
logger.info("회사 카테고리 생성", { companyCode, categoryId: result?.category_id });
|
|
|
|
|
return result!.category_id;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("회사 카테고리 생성 실패:", error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 메뉴 카테고리 확인 또는 생성 (메뉴 경로 전체)
|
|
|
|
|
*/
|
|
|
|
|
async ensureMenuCategory(
|
|
|
|
|
companyCode: string,
|
|
|
|
|
companyName: string,
|
|
|
|
|
menuPath: string[] // ["영업관리", "수주관리"]
|
|
|
|
|
): Promise<number> {
|
|
|
|
|
try {
|
|
|
|
|
if (menuPath.length === 0) {
|
|
|
|
|
return await this.ensureCompanyCategory(companyCode, companyName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let parentId = await this.ensureCompanyCategory(companyCode, companyName);
|
|
|
|
|
let currentLevel = 3;
|
|
|
|
|
|
|
|
|
|
for (const menuName of menuPath) {
|
|
|
|
|
// 현재 메뉴 카테고리 확인
|
|
|
|
|
const existing = await queryOne<{ category_id: number }>(
|
|
|
|
|
`SELECT category_id FROM multi_lang_category
|
|
|
|
|
WHERE category_name = $1 AND parent_id = $2`,
|
|
|
|
|
[menuName, parentId]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (existing) {
|
|
|
|
|
parentId = existing.category_id;
|
|
|
|
|
} else {
|
|
|
|
|
// 메뉴 카테고리 생성
|
|
|
|
|
const menuCode = `${companyCode}_${menuName}`.replace(/\s+/g, "_");
|
|
|
|
|
const keyPrefix = menuName.toLowerCase().replace(/\s+/g, "_");
|
|
|
|
|
|
|
|
|
|
const result = await queryOne<{ category_id: number }>(
|
|
|
|
|
`INSERT INTO multi_lang_category
|
|
|
|
|
(category_code, category_name, parent_id, level, key_prefix, description, sort_order, is_active, created_date)
|
|
|
|
|
VALUES ($1, $2, $3, $4, $5, $6, 0, 'Y', NOW())
|
|
|
|
|
RETURNING category_id`,
|
|
|
|
|
[menuCode, menuName, parentId, currentLevel, keyPrefix, `${menuName} 메뉴의 다국어`]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
logger.info("메뉴 카테고리 생성", { menuName, categoryId: result?.category_id });
|
|
|
|
|
parentId = result!.category_id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
currentLevel++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return parentId;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("메뉴 카테고리 생성 실패:", error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 메뉴 경로 조회 (menu_info에서 부모 메뉴까지)
|
|
|
|
|
*/
|
|
|
|
|
async getMenuPath(menuObjId: string): Promise<string[]> {
|
|
|
|
|
try {
|
|
|
|
|
const menus = await query<{ menu_name_kor: string; level: number }>(
|
|
|
|
|
`WITH RECURSIVE menu_path AS (
|
|
|
|
|
SELECT objid, parent_obj_id, menu_name_kor, 1 as level
|
|
|
|
|
FROM menu_info
|
|
|
|
|
WHERE objid = $1
|
|
|
|
|
UNION ALL
|
|
|
|
|
SELECT m.objid, m.parent_obj_id, m.menu_name_kor, mp.level + 1
|
|
|
|
|
FROM menu_info m
|
|
|
|
|
INNER JOIN menu_path mp ON m.objid = mp.parent_obj_id
|
|
|
|
|
WHERE m.parent_obj_id IS NOT NULL AND m.parent_obj_id != 0
|
|
|
|
|
)
|
|
|
|
|
SELECT menu_name_kor, level FROM menu_path
|
|
|
|
|
WHERE menu_name_kor IS NOT NULL
|
|
|
|
|
ORDER BY level DESC`,
|
|
|
|
|
[menuObjId]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return menus.map((m) => m.menu_name_kor);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("메뉴 경로 조회 실패:", error);
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 화면 라벨 다국어 키 자동 생성
|
|
|
|
|
*/
|
|
|
|
|
async generateScreenLabelKeys(params: {
|
|
|
|
|
screenId: number;
|
|
|
|
|
companyCode: string;
|
|
|
|
|
companyName: string;
|
|
|
|
|
menuObjId?: string;
|
|
|
|
|
labels: Array<{ componentId: string; label: string; type?: string }>;
|
|
|
|
|
}): Promise<Array<{ componentId: string; keyId: number; langKey: string }>> {
|
|
|
|
|
try {
|
|
|
|
|
logger.info("화면 라벨 다국어 키 자동 생성 시작", {
|
|
|
|
|
screenId: params.screenId,
|
|
|
|
|
companyCode: params.companyCode,
|
|
|
|
|
labelCount: params.labels.length,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 메뉴 경로 조회
|
|
|
|
|
const menuPath = params.menuObjId
|
|
|
|
|
? await this.getMenuPath(params.menuObjId)
|
|
|
|
|
: [];
|
|
|
|
|
|
|
|
|
|
// 메뉴 카테고리 확보
|
|
|
|
|
const categoryId = await this.ensureMenuCategory(
|
|
|
|
|
params.companyCode,
|
|
|
|
|
params.companyName,
|
|
|
|
|
menuPath
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 카테고리 경로 조회 (키 생성용)
|
|
|
|
|
const categoryPath = await this.getCategoryPath(categoryId);
|
|
|
|
|
const keyPrefixParts = categoryPath.map((c) => c.keyPrefix);
|
|
|
|
|
|
|
|
|
|
const results: Array<{ componentId: string; keyId: number; langKey: string }> = [];
|
|
|
|
|
|
|
|
|
|
for (const labelInfo of params.labels) {
|
|
|
|
|
// 라벨을 키 형태로 변환 (한글 → 스네이크케이스)
|
|
|
|
|
const keyMeaning = this.labelToKeyMeaning(labelInfo.label);
|
|
|
|
|
const langKey = [...keyPrefixParts, keyMeaning].join(".");
|
|
|
|
|
|
|
|
|
|
// 기존 키 확인
|
|
|
|
|
const existingKey = await queryOne<{ key_id: number }>(
|
|
|
|
|
`SELECT key_id FROM multi_lang_key_master
|
|
|
|
|
WHERE lang_key = $1 AND company_code = $2`,
|
|
|
|
|
[langKey, params.companyCode]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let keyId: number;
|
|
|
|
|
|
|
|
|
|
if (existingKey) {
|
|
|
|
|
keyId = existingKey.key_id;
|
|
|
|
|
logger.info("기존 키 사용", { langKey, keyId });
|
|
|
|
|
} else {
|
|
|
|
|
// 새 키 생성
|
|
|
|
|
const keyResult = await queryOne<{ key_id: number }>(
|
|
|
|
|
`INSERT INTO multi_lang_key_master
|
|
|
|
|
(company_code, lang_key, description, is_active, category_id, key_meaning, created_date, created_by)
|
|
|
|
|
VALUES ($1, $2, $3, 'Y', $4, $5, NOW(), 'system')
|
|
|
|
|
RETURNING key_id`,
|
|
|
|
|
[
|
|
|
|
|
params.companyCode,
|
|
|
|
|
langKey,
|
|
|
|
|
`화면 ${params.screenId}의 ${labelInfo.type || "라벨"}: ${labelInfo.label}`,
|
|
|
|
|
categoryId,
|
|
|
|
|
keyMeaning,
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
keyId = keyResult!.key_id;
|
|
|
|
|
|
|
|
|
|
// 한국어 텍스트 저장 (원문)
|
|
|
|
|
await query(
|
|
|
|
|
`INSERT INTO multi_lang_text (key_id, lang_code, lang_text, is_active, created_date, created_by)
|
|
|
|
|
VALUES ($1, 'KR', $2, 'Y', NOW(), 'system')
|
|
|
|
|
ON CONFLICT (key_id, lang_code) DO UPDATE SET lang_text = $2, updated_date = NOW()`,
|
|
|
|
|
[keyId, labelInfo.label]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
logger.info("새 키 생성", { langKey, keyId });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
results.push({
|
|
|
|
|
componentId: labelInfo.componentId,
|
|
|
|
|
keyId,
|
|
|
|
|
langKey,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.info("화면 라벨 다국어 키 생성 완료", {
|
|
|
|
|
screenId: params.screenId,
|
|
|
|
|
generatedCount: results.length,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return results;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("화면 라벨 다국어 키 생성 실패:", error);
|
|
|
|
|
throw new Error(
|
|
|
|
|
`화면 라벨 다국어 키 생성 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 라벨을 키 의미로 변환 (한글 → 스네이크케이스 또는 영문 유지)
|
|
|
|
|
*/
|
|
|
|
|
private labelToKeyMeaning(label: string): string {
|
|
|
|
|
// 이미 영문 스네이크케이스면 그대로 사용
|
|
|
|
|
if (/^[a-z][a-z0-9_]*$/.test(label)) {
|
|
|
|
|
return label;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 영문 일반이면 스네이크케이스로 변환
|
|
|
|
|
if (/^[A-Za-z][A-Za-z0-9 ]*$/.test(label)) {
|
|
|
|
|
return label.toLowerCase().replace(/\s+/g, "_");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 한글이면 간단한 변환 (특수문자 제거, 공백을 _로)
|
|
|
|
|
return label
|
|
|
|
|
.replace(/[^\w가-힣\s]/g, "")
|
|
|
|
|
.replace(/\s+/g, "_")
|
|
|
|
|
.toLowerCase();
|
|
|
|
|
}
|
2025-08-25 15:12:31 +09:00
|
|
|
}
|