화면 다국어 처리

This commit is contained in:
kjs
2026-01-14 15:33:57 +09:00
parent 26bb93ab6e
commit b5c2e85496
10 changed files with 657 additions and 273 deletions

View File

@@ -553,10 +553,24 @@ export const setUserLocale = async (
const { locale } = req.body;
if (!locale || !["ko", "en", "ja", "zh"].includes(locale)) {
if (!locale) {
res.status(400).json({
success: false,
message: "유효하지 않은 로케일입니다. (ko, en, ja, zh 중 선택)",
message: "로케일이 필요합니다.",
});
return;
}
// language_master 테이블에서 유효한 언어 코드인지 확인
const validLang = await queryOne<{ lang_code: string }>(
"SELECT lang_code FROM language_master WHERE lang_code = $1 AND is_active = 'Y'",
[locale]
);
if (!validLang) {
res.status(400).json({
success: false,
message: `유효하지 않은 로케일입니다: ${locale}`,
});
return;
}
@@ -3103,6 +3117,23 @@ export const updateProfile = async (
}
if (locale !== undefined) {
// language_master 테이블에서 유효한 언어 코드인지 확인
const validLang = await queryOne<{ lang_code: string }>(
"SELECT lang_code FROM language_master WHERE lang_code = $1 AND is_active = 'Y'",
[locale]
);
if (!validLang) {
res.status(400).json({
result: false,
error: {
code: "INVALID_LOCALE",
details: `유효하지 않은 로케일입니다: ${locale}`,
},
});
return;
}
updateFields.push(`locale = $${paramIndex}`);
updateValues.push(locale);
paramIndex++;

View File

@@ -1189,6 +1189,7 @@ export class MultiLangService {
/**
* 배치 번역 조회 (회사별 우선순위 적용)
* 우선순위: 회사별 키 > 공통 키(*)
* 폴백: 요청 언어 번역이 없으면 KR 번역 사용
*/
async getBatchTranslations(
params: BatchTranslationRequest
@@ -1233,16 +1234,10 @@ export class MultiLangService {
);
const result: Record<string, string> = {};
// 기본값으로 모든 키 설정
params.langKeys.forEach((key) => {
result[key] = key;
});
const processedKeys = new Set<string>();
// 우선순위 기반으로 번역 적용
// priority가 낮은 것(회사별)이 먼저 오므로, 먼저 처리된 키는 덮어쓰지 않음
const processedKeys = new Set<string>();
translations.forEach((translation) => {
const langKey = translation.lang_key;
if (params.langKeys.includes(langKey) && !processedKeys.has(langKey)) {
@@ -1251,6 +1246,55 @@ export class MultiLangService {
}
});
// 번역이 없는 키들에 대해 KR 폴백 조회 (요청 언어가 KR이 아닌 경우)
const missingKeys = params.langKeys.filter((key) => !processedKeys.has(key));
if (missingKeys.length > 0 && params.userLang !== "KR") {
logger.info("KR 폴백 번역 조회 시작", { missingCount: missingKeys.length });
const fallbackPlaceholders = missingKeys.map((_, i) => `$${i + 3}`).join(", ");
const fallbackTranslations = await query<{
lang_text: string;
lang_key: string;
company_code: string;
priority: number;
}>(
`SELECT mlt.lang_text, mlkm.lang_key, mlkm.company_code,
CASE WHEN mlkm.company_code = $2 THEN 1 ELSE 2 END as priority
FROM multi_lang_text mlt
INNER JOIN multi_lang_key_master mlkm ON mlt.key_id = mlkm.key_id
WHERE mlt.lang_code = 'KR'
AND mlt.is_active = $1
AND mlkm.lang_key IN (${fallbackPlaceholders})
AND mlkm.company_code IN ($2, '*')
AND mlkm.is_active = $1
ORDER BY mlkm.lang_key ASC, priority ASC`,
["Y", params.companyCode, ...missingKeys]
);
// KR 폴백 적용
const fallbackProcessed = new Set<string>();
fallbackTranslations.forEach((translation) => {
const langKey = translation.lang_key;
if (!result[langKey] && !fallbackProcessed.has(langKey)) {
result[langKey] = translation.lang_text;
fallbackProcessed.add(langKey);
}
});
logger.info("KR 폴백 번역 조회 완료", {
missingCount: missingKeys.length,
foundFallback: fallbackTranslations.length,
});
}
// 여전히 없는 키는 키 자체를 반환 (최후의 폴백)
params.langKeys.forEach((key) => {
if (!result[key]) {
result[key] = key;
}
});
logger.info("배치 번역 조회 완료", {
totalKeys: params.langKeys.length,
foundTranslations: translations.length,