다국어 시스템 가이드 작성

This commit is contained in:
kjs 2025-08-25 18:23:30 +09:00
parent 7bb7f1621f
commit bc24cccdf1
1 changed files with 970 additions and 0 deletions

View File

@ -0,0 +1,970 @@
# 다국어 관리 시스템 가이드
## 📋 목차
1. [시스템 개요](#시스템-개요)
2. [아키텍처 구조](#아키텍처-구조)
3. [데이터베이스 구조](#데이터베이스-구조)
4. [핵심 컴포넌트](#핵심-컴포넌트)
5. [사용법 가이드](#사용법-가이드)
6. [페이지 적용 방법](#페이지-적용-방법)
7. [새로운 다국어 키 추가](#새로운-다국어-키-추가)
8. [문제 해결](#문제-해결)
9. [모범 사례](#모범-사례)
## 🎯 시스템 개요
### 다국어 시스템이란?
현재 ERP 시스템은 한국어와 영어를 지원하는 다국어 시스템을 구축하고 있습니다. 사용자가 설정한 언어에 따라 UI의 모든 텍스트가 자동으로 번역되어 표시됩니다.
### 지원 언어
- **한국어 (KR)**: 기본 언어
- **영어 (EN)**: 사용자 설정 가능
### 주요 특징
- **실시간 번역**: 페이지 로드 시 즉시 번역 적용
- **배치 처리**: 여러 키를 한 번에 조회하여 성능 최적화
- **캐싱 시스템**: 번역 결과를 메모리에 캐싱하여 중복 요청 방지
- **폴백 시스템**: 번역 실패 시 기본 텍스트 표시
## 🏗️ 아키텍처 구조
### 전체 구조도
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Frontend │ │ Backend │ │ Database │
│ │ │ │ │ │
│ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │
│ │ useMultiLang│ │ │ │ Multilang │ │ │ │ multilang │ │
│ │ Hook │ │ │ │ Controller │ │ │ │ Table │ │
│ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │
│ │ │ │ │ │
│ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │
│ │ Multilang │ │ │ │ Multilang │ │ │ │ lang_keys │ │
│ │ Utils │ │ │ │ Service │ │ │ │ Table │ │
│ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │
│ │ │ │ │ │
│ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │
│ │ UI │ │ │ │ Batch API │ │ │ │ translations│ │
│ │ Components │ │ │ │ Endpoint │ │ │ │ Table │ │
│ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
### 데이터 흐름
1. **사용자 언어 설정**: `useMultiLang` 훅에서 사용자 언어 확인
2. **다국어 키 정의**: 컴포넌트에서 필요한 다국어 키 상수 정의
3. **배치 요청**: 여러 키를 한 번에 백엔드로 전송
4. **번역 조회**: 백엔드에서 데이터베이스에서 번역 데이터 조회
5. **결과 반환**: 번역된 텍스트를 프론트엔드로 반환
6. **UI 업데이트**: 번역된 텍스트로 컴포넌트 렌더링
## 🗄️ 데이터베이스 구조
### 실제 테이블 구조
#### 1. language_master (언어 마스터)
```sql
CREATE TABLE public.language_master (
lang_code varchar(10) NOT NULL PRIMARY KEY, -- 언어 코드 (KR, EN)
lang_name varchar(50) NOT NULL, -- 언어명 (Korean, English)
lang_native varchar(50) NOT NULL, -- 원어명 (한국어, English)
is_active bpchar(1) DEFAULT 'Y', -- 활성화 여부
sort_order int4 DEFAULT 0, -- 정렬 순서
created_date timestamp DEFAULT CURRENT_TIMESTAMP,
created_by varchar(50),
updated_date timestamp DEFAULT CURRENT_TIMESTAMP,
updated_by varchar(50)
);
```
#### 2. multi_lang_key_master (다국어 키 마스터)
```sql
CREATE TABLE public.multi_lang_key_master (
key_id serial4 NOT NULL PRIMARY KEY, -- 키 ID (자동 증가)
company_code varchar(20) DEFAULT '*' NOT NULL, -- 회사 코드 (* = 공통)
lang_key varchar(100) NOT NULL, -- 다국어 키 (예: button.add)
description text, -- 키 설명
is_active bpchar(1) DEFAULT 'Y', -- 활성화 여부
menu_name varchar(50), -- 메뉴명 (사용하지 않음)
created_date timestamp DEFAULT CURRENT_TIMESTAMP,
created_by varchar(50),
updated_date timestamp DEFAULT CURRENT_TIMESTAMP,
updated_by varchar(50),
CONSTRAINT uk_lang_key_company UNIQUE (company_code, lang_key)
);
```
#### 3. multi_lang_text (다국어 텍스트)
```sql
CREATE TABLE public.multi_lang_text (
text_id serial4 NOT NULL PRIMARY KEY, -- 텍스트 ID (자동 증가)
key_id int4 NOT NULL, -- 키 마스터의 key_id
lang_code varchar(10) NOT NULL, -- 언어 코드 (KR, EN)
lang_text text NOT NULL, -- 번역된 텍스트
is_active bpchar(1) DEFAULT 'Y', -- 활성화 여부
created_date timestamp DEFAULT CURRENT_TIMESTAMP,
created_by varchar(50),
updated_date timestamp DEFAULT CURRENT_TIMESTAMP,
updated_by varchar(50),
CONSTRAINT multi_lang_text_key_id_lang_code_key UNIQUE (key_id, lang_code),
CONSTRAINT multi_lang_text_key_id_fkey FOREIGN KEY (key_id)
REFERENCES multi_lang_key_master(key_id) ON DELETE CASCADE,
CONSTRAINT multi_lang_text_lang_code_fkey FOREIGN KEY (lang_code)
REFERENCES language_master(lang_code)
);
```
### 테이블 관계도
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ language_master │ │multi_lang_key_ │ │ multi_lang_text │
│ │ │ master │ │ │
│ - lang_code │ │ - key_id │ │ - text_id │
│ - lang_name │ │ - company_code │ │ - lang_key │
│ - lang_native │ │ - description │ │ - key_id │
│ - is_active │ │ - is_active │ │ - lang_code │
│ - sort_order │ │ - created_date │ │ - lang_text │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
│ │ │
└───────────────────────┼───────────────────────┘
┌─────────────────┐
│ 외래키 관계 │
│ │
│ lang_code │
│ key_id │
└─────────────────┘
```
### 데이터 저장 예시
#### 1. 언어 마스터 데이터
```sql
INSERT INTO language_master (lang_code, lang_name, lang_native) VALUES
('KR', 'Korean', '한국어'),
('EN', 'English', 'English');
```
#### 2. 다국어 키 등록
```sql
INSERT INTO multi_lang_key_master (lang_key, company_code, description) VALUES
('button.add', '*', '추가 버튼'),
('button.edit', '*', '수정 버튼'),
('menu.title', '*', '메뉴 제목');
```
#### 3. 번역 텍스트 등록
```sql
-- 한국어 번역
INSERT INTO multi_lang_text (key_id, lang_code, lang_text)
SELECT km.key_id, 'KR',
CASE km.lang_key
WHEN 'button.add' THEN '추가'
WHEN 'button.edit' THEN '수정'
WHEN 'menu.title' THEN '메뉴 관리'
END
FROM multi_lang_key_master km
WHERE km.lang_key IN ('button.add', 'button.edit', 'menu.title')
AND km.company_code = '*';
-- 영어 번역
INSERT INTO multi_lang_text (key_id, lang_code, lang_text)
SELECT km.key_id, 'EN',
CASE km.lang_key
WHEN 'button.add' THEN 'Add'
WHEN 'button.edit' THEN 'Edit'
WHEN 'menu.title' THEN 'Menu Management'
END
FROM multi_lang_key_master km
WHERE km.lang_key IN ('button.add', 'button.edit', 'menu.title')
AND km.company_code = '*';
```
## 🔧 핵심 컴포넌트
### 1. useMultiLang 훅
```typescript
// frontend/hooks/useMultiLang.ts
export const useMultiLang = (options: { companyCode?: string } = {}) => {
const [userLang, setUserLang] = useState<string | null>(null);
// 사용자 언어 조회 및 설정
// 전역 언어 상태 동기화
// 언어 변경 처리
return {
userLang, // 현재 사용자 언어 (KR, EN)
getText, // 다국어 텍스트 조회 함수
changeLang, // 언어 변경 함수
companyCode, // 회사 코드
};
};
```
**주요 기능:**
- 사용자 언어 설정 및 조회
- 전역 언어 상태 관리
- 언어 변경 시 자동 동기화
**사용 예시:**
```typescript
const { userLang, getText, changeLang } = useMultiLang({ companyCode: "*" });
// 언어 변경
await changeLang("EN");
// 다국어 텍스트 조회
const text = await getText("menu.management", "button.add");
```
### 2. Multilang Utils
```typescript
// frontend/lib/utils/multilang.ts
export const MENU_MANAGEMENT_KEYS = {
TITLE: "menu.management.title",
BUTTON_ADD: "button.add",
BUTTON_EDIT: "button.edit",
// ... 더 많은 키들
} as const;
// 배치 조회 함수
export async function getMultilangText(
key: string,
companyCode: string = "*",
menuCode: string = "MENU_MANAGEMENT",
userLang: string = "KR"
): Promise<string>;
// 동기 조회 함수 (캐시에서만)
export function getMultilangTextSync(
key: string,
companyCode: string = "*",
menuCode: string = "MENU_MANAGEMENT",
userLang: string = "KR"
): string;
```
**주요 기능:**
- 다국어 키 상수 정의
- 배치 처리로 성능 최적화
- 캐싱 시스템으로 중복 요청 방지
- 동기/비동기 조회 지원
### 3. Backend API
```typescript
// backend/src/controllers/multilangController.ts
@Post("/batch")
async getBatchTranslations(
@Body() body: { langKeys: string[] },
@Query() query: { companyCode: string; menuCode: string; userLang: string }
): Promise<ApiResponse<Record<string, string>>>
```
**API 엔드포인트:**
- **POST** `/multilang/batch` - 여러 키를 한 번에 조회
- **GET** `/multilang/:key` - 개별 키 조회 (사용하지 않음)
**요청 파라미터:**
```typescript
{
langKeys: ["button.add", "button.edit", "menu.title"],
companyCode: "*", // 회사 코드 (* = 전체)
menuCode: "MENU_MANAGEMENT", // 메뉴 코드
userLang: "KR" // 사용자 언어
}
```
**응답 형식:**
```typescript
{
success: true,
data: {
"button.add": "추가",
"button.edit": "수정",
"menu.title": "메뉴 관리"
}
}
```
## 📖 사용법 가이드
### 1. 기본 사용법
#### 단계별 가이드
1. **다국어 키 상수 정의**
2. **컴포넌트에서 다국어 텍스트 로드**
3. **UI에 번역된 텍스트 적용**
#### 예시 코드
```typescript
// 1. 다국어 키 상수 정의
const MENU_KEYS = ["menu.title", "button.add", "button.edit"];
// 2. 다국어 텍스트 로드
const [uiTexts, setUiTexts] = useState<Record<string, string>>({});
useEffect(() => {
const loadTexts = async () => {
const response = await apiClient.post("/multilang/batch", {
langKeys: MENU_KEYS,
companyCode: "*",
menuCode: "MENU_MANAGEMENT",
userLang: userLang,
});
if (response.data.success) {
setUiTexts(response.data.data);
}
};
if (userLang) {
loadTexts();
}
}, [userLang]);
// 3. UI에 적용
const getText = (key: string) => uiTexts[key] || key;
return (
<div>
<h1>{getText("menu.title")}</h1>
<button>{getText("button.add")}</button>
</div>
);
```
### 2. 고급 사용법
#### 배치 처리 최적화
```typescript
// 여러 키를 한 번에 조회하여 API 호출 최소화
const LANG_KEYS = [
"form.title",
"form.name.label",
"form.name.placeholder",
"form.submit.button",
];
// 한 번의 API 호출로 모든 키 조회
const translations = await getMultilangTextBatch(LANG_KEYS);
```
#### 폴백 시스템
```typescript
const getText = (key: string, fallback?: string) => {
// 1. 번역된 텍스트 우선
if (uiTexts[key]) return uiTexts[key];
// 2. 폴백 텍스트 사용
if (fallback) return fallback;
// 3. 키 자체 반환 (디버깅용)
return key;
};
```
## 🚀 페이지 적용 방법
### 1. 새로운 페이지에 다국어 적용하기
#### Step 1: 다국어 키 정의
```typescript
// constants/pageKeys.ts
export const PAGE_KEYS = {
// 페이지 제목
TITLE: "page.title",
DESCRIPTION: "page.description",
// 폼 요소
FORM_NAME: "form.name",
FORM_EMAIL: "form.email",
FORM_SUBMIT: "form.submit",
// 메시지
SUCCESS_MESSAGE: "message.success",
ERROR_MESSAGE: "message.error",
} as const;
```
#### Step 2: 컴포넌트에서 다국어 텍스트 로드
```typescript
// components/MyPage.tsx
import { PAGE_KEYS } from "@/constants/pageKeys";
export const MyPage: React.FC = () => {
const { userLang } = useMultiLang();
const [uiTexts, setUiTexts] = useState<Record<string, string>>({});
// 다국어 텍스트 로드
useEffect(() => {
const loadTexts = async () => {
if (!userLang) return;
const response = await apiClient.post("/multilang/batch", {
langKeys: Object.values(PAGE_KEYS),
companyCode: "*",
menuCode: "MY_PAGE",
userLang: userLang,
});
if (response.data.success) {
setUiTexts(response.data.data);
}
};
loadTexts();
}, [userLang]);
// 텍스트 가져오기 함수
const getText = (key: string) => uiTexts[key] || key;
return (
<div>
<h1>{getText(PAGE_KEYS.TITLE)}</h1>
<p>{getText(PAGE_KEYS.DESCRIPTION)}</p>
<form>
<label>{getText(PAGE_KEYS.FORM_NAME)}</label>
<input placeholder={getText(PAGE_KEYS.FORM_NAME)} />
<button type="submit">{getText(PAGE_KEYS.FORM_SUBMIT)}</button>
</form>
</div>
);
};
```
#### Step 3: 데이터베이스에 번역 데이터 등록
```sql
-- 1. 다국어 키 마스터에 키 등록
INSERT INTO multi_lang_key_master (
lang_key,
company_code,
description,
is_active,
created_date,
created_by
) VALUES
('page.title', '*', '페이지 제목', 'Y', now(), 'system'),
('page.description', '*', '페이지 설명', 'Y', now(), 'system'),
('form.name', '*', '이름', 'Y', now(), 'system'),
('form.email', '*', '이메일', 'Y', now(), 'system'),
('form.submit', '*', '제출', 'Y', now(), 'system');
-- 2. 한국어 번역 텍스트 등록
INSERT INTO multi_lang_text (
key_id,
lang_code,
lang_text,
is_active,
created_date,
created_by
)
SELECT
km.key_id,
'KR',
CASE km.lang_key
WHEN 'page.title' THEN '내 페이지'
WHEN 'page.description' THEN '이것은 내 페이지입니다'
WHEN 'form.name' THEN '이름'
WHEN 'form.email' THEN '이메일'
WHEN 'form.submit' THEN '제출'
END,
'Y',
now(),
'system'
FROM multi_lang_key_master km
WHERE km.lang_key IN ('page.title', 'page.description', 'form.name', 'form.email', 'form.submit')
AND km.company_code = '*';
-- 3. 영어 번역 텍스트 등록
INSERT INTO multi_lang_text (
key_id,
lang_code,
lang_text,
is_active,
created_date,
created_by
)
SELECT
km.key_id,
'EN',
CASE km.lang_key
WHEN 'page.title' THEN 'My Page'
WHEN 'page.description' THEN 'This is my page'
WHEN 'form.name' THEN 'Name'
WHEN 'form.email' THEN 'Email'
WHEN 'form.submit' THEN 'Submit'
END,
'Y',
now(),
'system'
FROM multi_lang_key_master km
WHERE km.lang_key IN ('page.title', 'page.description', 'form.name', 'form.email', 'form.submit')
AND km.company_code = '*';
```
### 2. 기존 페이지에 다국어 적용하기
#### Step 1: 하드코딩된 텍스트 찾기
```typescript
// 변경 전
return (
<div>
<h1>메뉴 관리</h1>
<button>추가</button>
<button>수정</button>
</div>
);
// 변경 후
return (
<div>
<h1>{getText("menu.management.title")}</h1>
<button>{getText("button.add")}</button>
<button>{getText("button.edit")}</button>
</div>
);
```
#### Step 2: 다국어 키 상수 추가
```typescript
// 기존 키에 새로운 키 추가
export const EXISTING_KEYS = [
// 기존 키들...
"menu.management.title",
"button.add",
"button.edit",
];
```
#### Step 3: 번역 데이터 등록
```sql
-- 1. 다국어 키 마스터에 키 등록
INSERT INTO multi_lang_key_master (
lang_key,
company_code,
description,
is_active,
created_date,
created_by
) VALUES
('menu.management.title', '*', '메뉴 관리 제목', 'Y', now(), 'system'),
('button.add', '*', '추가 버튼', 'Y', now(), 'system'),
('button.edit', '*', '수정 버튼', 'Y', now(), 'system');
-- 2. 한국어 번역 텍스트 등록
INSERT INTO multi_lang_text (
key_id,
lang_code,
lang_text,
is_active,
created_date,
created_by
)
SELECT
km.key_id,
'KR',
CASE km.lang_key
WHEN 'menu.management.title' THEN '메뉴 관리'
WHEN 'button.add' THEN '추가'
WHEN 'button.edit' THEN '수정'
END,
'Y',
now(),
'system'
FROM multi_lang_key_master km
WHERE km.lang_key IN ('menu.management.title', 'button.add', 'button.edit')
AND km.company_code = '*';
-- 3. 영어 번역 텍스트 등록
INSERT INTO multi_lang_text (
key_id,
lang_code,
lang_text,
is_active,
created_date,
created_by
)
SELECT
km.key_id,
'EN',
CASE km.lang_key
WHEN 'menu.management.title' THEN 'Menu Management'
WHEN 'button.add' THEN 'Add'
WHEN 'button.edit' THEN 'Edit'
END,
'Y',
now(),
'system'
FROM multi_lang_key_master km
WHERE km.lang_key IN ('menu.management.title', 'button.add', 'button.edit')
AND km.company_code = '*';
```
## 새로운 다국어 키 추가
### 1. 프론트엔드에서 키 추가
#### Step 1: 키 상수 정의
```typescript
// constants/multilang.ts
export const NEW_FEATURE_KEYS = {
// 새로운 기능 관련 키들
FEATURE_TITLE: "new.feature.title",
FEATURE_DESCRIPTION: "new.feature.description",
FEATURE_BUTTON: "new.feature.button",
} as const;
```
#### Step 2: 컴포넌트에서 사용
```typescript
import { NEW_FEATURE_KEYS } from "@/constants/multilang";
const MyComponent = () => {
const getText = (key: string) => uiTexts[key] || key;
return (
<div>
<h2>{getText(NEW_FEATURE_KEYS.FEATURE_TITLE)}</h2>
<p>{getText(NEW_FEATURE_KEYS.FEATURE_DESCRIPTION)}</p>
<button>{getText(NEW_FEATURE_KEYS.FEATURE_BUTTON)}</button>
</div>
);
};
```
### 2. 백엔드에서 번역 데이터 등록
#### Step 1: 다국어 키 등록
```sql
-- multi_lang_key_master 테이블에 새로운 키 등록
INSERT INTO multi_lang_key_master (
lang_key,
company_code,
description,
is_active,
created_date,
created_by
) VALUES
('new.feature.title', '*', '새로운 기능 제목', 'Y', now(), 'system'),
('new.feature.description', '*', '새로운 기능 설명', 'Y', now(), 'system'),
('new.feature.button', '*', '새로운 기능 버튼', 'Y', now(), 'system');
```
#### Step 2: 번역 텍스트 등록
```sql
-- 한국어 번역 텍스트 등록
INSERT INTO multi_lang_text (
key_id,
lang_code,
lang_text,
is_active,
created_date,
created_by
)
SELECT
km.key_id,
'KR',
CASE km.lang_key
WHEN 'new.feature.title' THEN '새로운 기능'
WHEN 'new.feature.description' THEN '이것은 새로운 기능입니다'
WHEN 'new.feature.button' THEN '시작하기'
END,
'Y',
now(),
'system'
FROM multi_lang_key_master km
WHERE km.lang_key IN ('new.feature.title', 'new.feature.description', 'new.feature.button')
AND km.company_code = '*';
-- 영어 번역 텍스트 등록
INSERT INTO multi_lang_text (
key_id,
lang_code,
lang_text,
is_active,
created_date,
created_by
)
SELECT
km.key_id,
'EN',
CASE km.lang_key
WHEN 'new.feature.title' THEN 'New Feature'
WHEN 'new.feature.description' THEN 'This is a new feature'
WHEN 'new.feature.button' THEN 'Get Started'
END,
'Y',
now(),
'system'
FROM multi_lang_key_master km
WHERE km.lang_key IN ('new.feature.title', 'new.feature.description', 'new.feature.button')
AND km.company_code = '*';
```
## 🔍 문제 해결
### 1. 일반적인 문제들
#### 문제: 키가 그대로 표시됨
```typescript
// 문제 상황
<h1>menu.management.title</h1>;
// 원인: 번역 데이터가 로드되지 않음
// 해결: 다국어 텍스트 로드 확인
console.log("uiTexts:", uiTexts);
console.log("userLang:", userLang);
```
#### 문제: 일부만 번역됨
```typescript
// 원인: 일부 키만 번역 데이터에 등록됨
// 해결: 모든 키에 대한 번역 데이터 확인
SELECT km.lang_key, t.lang_code, t.lang_text
FROM multi_lang_key_master km
LEFT JOIN multi_lang_text t ON km.key_id = t.key_id
WHERE km.lang_key IN ('key1', 'key2', 'key3')
AND km.company_code = '*';
```
#### 문제: 언어 변경이 반영되지 않음
```typescript
// 원인: useMultiLang 훅의 의존성 배열 문제
// 해결: userLang 변경 시 다국어 텍스트 재로드
useEffect(() => {
if (userLang) {
loadTexts();
}
}, [userLang]); // userLang 의존성 추가
```
### 2. 디버깅 방법
#### 콘솔 로그 확인
```typescript
// 다국어 텍스트 로드 과정 추적
console.log("🌐 다국어 텍스트 로드 시작:", {
userLang,
keys: MENU_KEYS,
uiTextsCount: Object.keys(uiTexts).length,
});
// API 응답 확인
console.log("📡 API 응답:", response.data);
// 최종 상태 확인
console.log("✅ 최종 uiTexts:", uiTexts);
```
#### 네트워크 탭 확인
- **API 호출**: `/multilang/batch` 엔드포인트 호출 확인
- **요청 파라미터**: langKeys, companyCode, menuCode, userLang 확인
- **응답 데이터**: 번역된 텍스트가 올바르게 반환되는지 확인
#### 데이터베이스 직접 확인
```sql
-- 특정 키의 번역 데이터 확인
SELECT km.lang_key, t.lang_code, t.lang_text, km.description
FROM multi_lang_key_master km
LEFT JOIN multi_lang_text t ON km.key_id = t.key_id
WHERE km.lang_key = 'button.add'
AND km.company_code = '*'
AND t.lang_code = 'KR';
-- 특정 회사의 모든 번역 데이터 확인
SELECT km.lang_key, t.lang_code, t.lang_text, km.description
FROM multi_lang_key_master km
LEFT JOIN multi_lang_text t ON km.key_id = t.key_id
WHERE km.company_code = '*'
AND t.lang_code = 'KR';
```
## 📚 모범 사례
### 1. 키 명명 규칙
#### 계층 구조 사용
```typescript
// 좋은 예시
export const KEYS = {
// 페이지 레벨
PAGE_TITLE: "page.title",
PAGE_DESCRIPTION: "page.description",
// 폼 레벨
FORM_TITLE: "form.title",
FORM_NAME: "form.name",
FORM_EMAIL: "form.email",
// 버튼 레벨
BUTTON_SUBMIT: "button.submit",
BUTTON_CANCEL: "button.cancel",
// 메시지 레벨
MESSAGE_SUCCESS: "message.success",
MESSAGE_ERROR: "message.error",
};
```
#### 일관된 접두사 사용
```typescript
// 일관된 접두사로 그룹화
export const KEYS = {
// 공통 UI 요소
COMMON_LOADING: "common.loading",
COMMON_ERROR: "common.error",
COMMON_SUCCESS: "common.success",
// 특정 기능
FEATURE_TITLE: "feature.title",
FEATURE_DESCRIPTION: "feature.description",
};
```
### 2. 성능 최적화
#### 배치 처리 사용
```typescript
// ❌ 나쁜 예시: 개별 요청
const text1 = await getText("key1");
const text2 = await getText("key2");
const text3 = await getText("key3");
// ✅ 좋은 예시: 배치 요청
const keys = ["key1", "key2", "key3"];
const translations = await getBatchTexts(keys);
```
#### 캐싱 활용
```typescript
// 번역 결과를 상태에 저장하여 재사용
const [uiTexts, setUiTexts] = useState<Record<string, string>>({});
// 한 번 로드한 후 재사용
useEffect(() => {
if (Object.keys(uiTexts).length === 0) {
loadTexts();
}
}, []);
```
### 3. 에러 처리
#### 폴백 시스템 구현
```typescript
const getText = (key: string, fallback?: string) => {
// 1. 번역된 텍스트 우선
if (uiTexts[key]) return uiTexts[key];
// 2. 폴백 텍스트 사용
if (fallback) return fallback;
// 3. 키 자체 반환 (디버깅용)
console.warn(`번역 키를 찾을 수 없음: ${key}`);
return key;
};
```
#### 로딩 상태 관리
```typescript
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const loadTexts = async () => {
try {
setLoading(true);
setError(null);
const response = await apiClient.post("/multilang/batch", { ... });
if (response.data.success) {
setUiTexts(response.data.data);
} else {
setError(response.data.message);
}
} catch (err) {
setError('다국어 텍스트 로드 실패');
} finally {
setLoading(false);
}
};
```
## 📝 요약
### 핵심 포인트
1. **useMultiLang 훅**을 사용하여 사용자 언어 설정 관리
2. **배치 API**를 사용하여 여러 키를 한 번에 조회
3. **키 상수**를 정의하여 일관성 유지
4. **폴백 시스템**을 구현하여 번역 실패 시에도 UI 표시
5. **캐싱**을 활용하여 성능 최적화
### 개발 워크플로우
1. 다국어 키 상수 정의
2. 컴포넌트에서 다국어 텍스트 로드
3. UI에 번역된 텍스트 적용
4. 데이터베이스에 번역 데이터 등록
5. 테스트 및 디버깅
### 주의사항
- 키 명명 규칙을 일관되게 유지
- 배치 처리를 사용하여 API 호출 최소화
- 폴백 시스템을 구현하여 사용자 경험 보장
- 성능을 고려한 캐싱 전략 수립
이 가이드를 따라하면 새로운 개발자도 쉽게 다국어 시스템을 이해하고 적용할 수 있을 것입니다.