1
0
forked from baron/baron-sso
Files
baron-sso/docs/i18n.md

270 lines
11 KiB
Markdown

# Baron SSO i18n(국제화) 처리 정책
## 1. 개요 (Overview)
본 문서는 Baron SSO 시스템(Backend, UserFront, AdminFront, DevFront)의 다국어 지원을 위한 표준 정책을 정의합니다.
모든 UI 텍스트와 서버 메시지는 **하드코딩을 지양하고, 약속된 코드값(Key)을 기반으로 렌더링**해야 합니다.
---
## 2. 기본 원칙 (Core Principles)
1. **Backend는 Key만 전달한다**: API 응답에는 사용자에게 보여질 텍스트(메시지)를 포함하지 않는다. 대신 기계가 해석 가능한 `code`를 반환한다.
2. **Frontend가 렌더링 주체다**: 모든 번역 데이터(Dictionary)는 프론트엔드 애플리케이션이 보유하며, 백엔드로부터 받은 `code`나 UI의 키값을 매핑하여 적절한 언어로 표시한다.
3. **URL 기반 로케일 격리**: 언어 설정은 URL 경로(`/{locale}/...`)에 명시적으로 드러나야 한다.
4. **포맷 및 관리**:
* 모든 리소스는 **TOML** 포맷을 사용한다. (주석 가능, 가독성 우수)
* **`template.toml`**을 기준(Master)으로 삼아, 각 언어 파일(`ko.toml`, `en.toml`)이 키를 빠짐없이 구현했는지 관리한다.
---
## 3. 키(Key) 명명 규칙 (Naming Convention)
메시지 키는 계층 구조를 가지며 `snake_case``dot(.)` 표기법을 혼용하여 체계적으로 관리합니다.
TOML에서는 `[Section]`을 사용하여 계층을 표현합니다.
### 3.1 카테고리 (Category)
가장 상위 레벨에서 메시지의 성격을 정의합니다.
| Prefix (Section) | 설명 | 예시 |
| :--- | :--- | :--- |
| **`[ui]`** | 버튼, 레이블, 메뉴 등 짧은 단어 | `ui.btn.save`, `ui.nav.dashboard` |
| **`[msg]`** | 문장형 알림, 설명 텍스트, 다이얼로그 내용 | `msg.info.saved_success` |
| **`[err]`** | 백엔드 에러 코드 또는 프론트 유효성 검사 에러 | `err.auth.user_not_found` |
| **`[domain]`** | 비즈니스 도메인 용어 (명사 위주) | `domain.user.role` |
| **`[unit]`** | 시간, 화폐, 데이터 단위 | `unit.time.sec` |
### 3.2 TOML 예시 (`template.toml`)
```toml
# UI Elements
[ui]
[ui.btn]
save = ""
cancel = ""
[ui.label]
password = ""
# Messages
[msg]
[msg.info]
saved_success = ""
# Errors (Backend Codes)
[err]
[err.auth]
user_not_found = ""
```
---
## 4. 로케일 및 라우팅 정책 (Locale & Routing)
### 4.1 지원 언어 및 Fallback
* **기본 언어(Default)**: `en` (영어)
* **지원 언어**: `ko` (한국어), `en` (영어)
* **매핑 로직**:
* 브라우저 설정이 `ko`, `ko-KR` 인 경우 → **한국어 (`ko`)** 노출
* 그 외 모든 언어(`ja`, `zh`, `en-US` 등) → **영어 (`en`)** 노출
### 4.2 URL 구조
모든 페이지는 URL의 첫 번째 경로(Path Segment)로 언어를 구분합니다.
* `https://sso.baron.io/ko/login` (한국어)
* `https://sso.baron.io/en/dashboard` (영어)
### 4.3 라우팅 및 리다이렉트 동작
1. **Root 접속 시 (`/`)**:
* 사용자의 브라우저 `navigator.language`를 감지합니다.
* `ko` 계열이면 `/ko/dashboard`로 리다이렉트합니다.
* 그 외에는 `/en/dashboard`로 리다이렉트합니다.
* *단, 이전에 언어를 선택한 쿠키나 로컬스토리지 값이 있다면 그 값을 우선합니다.*
* 로컬스토리지 키는 **`locale`**을 사용합니다.
2. **언어 변경 시**:
* 모든 화면에는 **언어 선택기(Language Selector)**가 노출되어야 합니다.
* 변경 시 해당 언어의 URL로 이동(`window.location` 변경 혹은 Router Push)하며, 선택된 언어를 로컬스토리지에 저장합니다.
---
## 5. 아키텍처 및 데이터 흐름
### 5.1 백엔드 (Go Fiber)
백엔드는 번역을 수행하지 않습니다. 오직 상황에 맞는 **Error Code**만 반환합니다.
**응답 예시 (401 Unauthorized):**
```json
{
"error": "Invalid password",
"code": "auth.invalid_credentials", // TOML의 [err.auth] invalid_credentials 와 매핑
"details": null
}
```
### 5.2 프론트엔드 (React / Flutter)
#### 5.2.1 리소스 공유 및 변환
* TOML은 개발 및 관리 편의를 위한 포맷입니다.
* **Build Time** 또는 **Runtime**에 TOML 파일을 로드하여 사용합니다.
* **React (Vite)**: `vite-plugin-toml` 등을 사용하거나, 빌드 스크립트로 JSON 변환 후 `i18next`에 주입.
* **Flutter**: `toml` 패키지를 사용하여 로드하거나, 전처리 스크립트로 ARB/JSON 변환 후 `easy_localization` 사용.
#### 5.2.2 관리 프로세스 (Template & CI)
1. **`template.toml`**: 개발자가 새로운 번역 키를 추가할 때 반드시 이 파일에 먼저 정의해야 합니다.
2. **`ko.toml`, `en.toml`**: 템플릿의 키를 바탕으로 실제 번역 값을 채워 넣습니다.
3. **UserFront 런타임 리소스 동기화**:
* UserFront는 런타임에 `userfront/assets/translations/*.toml`을 직접 읽습니다.
* 따라서 `locales/ko.toml`, `locales/en.toml`, `locales/template.toml`을 수정한 뒤에는 반드시 아래 동기화 스크립트를 실행해야 합니다.
* 실행 명령:
```bash
./scripts/sync_userfront_locales.sh
```
* 이 단계가 누락되면 루트 SoT와 UserFront 실제 표시 문구가 어긋날 수 있습니다.
4. **React 공통 locale 레이어**:
* React 계열 프런트(`adminfront`, `devfront`, `orgfront`)는 `common/locales/*.toml`을 공통 문구 레이어로 사용합니다.
* 공통 key는 `ui.common.*`, `msg.common.*` 범위에만 둡니다.
* 각 앱의 `src/locales/*.toml`은 앱 전용 문구를 유지하고, 로딩 시 `common locale -> app locale override` 순서로 merge 합니다.
3. **CI 검증 (Verification)**:
* **Level 1: 리소스 동기화 검사 (`template` vs `lang`)**
* `locales/*.toml`과 `common/locales/*.toml` 각각에 대해 `template.toml`에 있는 모든 키가 `ko.toml`, `en.toml`에 존재하는지 재귀적으로 검사합니다.
* 누락 시 빌드 실패.
* **Level 2: 코드 사용성 검사 (`code` vs `template`)**
* 전체 프론트엔드 소스코드(`src/**/*.{ts,tsx}`, `lib/**/*.dart`)를 스캔하여 번역 함수(`t('key')`, `'key'.tr()`)에 사용된 키를 추출합니다.
* **Missing Key**: 코드에는 있는데 해당 레이어의 `template.toml`에 없는 키를 검출하여 경고 또는 에러를 발생시킵니다.
* **Unused Key**: 각 `template.toml`에는 있는데 코드 어디에서도 쓰이지 않는 키를 리포트하여 정리할 수 있게 합니다.
#### 5.2.3 React (Admin/Dev) 구현 가이드
* **패키지 설치**:
```bash
npm install i18next react-i18next i18next-browser-languagedetector
npm install -D vite-plugin-toml
```
* **Vite 설정 (`vite.config.ts`)**:
```ts
import { defineConfig } from 'vite';
import { plugin as toml } from 'vite-plugin-toml';
export default defineConfig({
plugins: [toml], // .toml 파일을 모듈로 import 가능하게 함
});
```
* **i18n 초기화 (`src/i18n.ts`)**:
```ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
// TOML 파일 직접 import (JSON 객체로 변환됨)
import ko from './locales/ko.toml';
import en from './locales/en.toml';
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources: {
ko: { translation: ko },
en: { translation: en },
},
fallbackLng: 'en',
interpolation: {
escapeValue: false,
},
});
export default i18n;
```
#### 5.2.4 Flutter (User) 구현 가이드
Flutter는 런타임에 TOML을 파싱하기 위해 `toml` 패키지와 `easy_localization`의 커스텀 로더를 사용합니다.
#### 5.2.5 UserFront 에러 표시 정책 (Production)
UserFront(`/error`)는 프로덕션에서 다음 규칙으로 에러를 표시합니다.
1. **Internal whitelist 코드**
- `msg.userfront.error.whitelist.{code}` 메시지를 노출합니다.
2. **ORY bypass 코드**
- `msg.userfront.error.ory.{code}` 메시지를 노출합니다.
3. **그 외 코드**
- `unknown_error`로 처리하고 일반 안내 문구(`msg.userfront.error.detail_contact`)를 노출합니다.
코드 집합은 `userfront/lib/core/constants/error_whitelist.dart`를 단일 기준으로 유지합니다.
* **패키지 추가 (`pubspec.yaml`)**:
```yaml
dependencies:
easy_localization: ^3.0.0
toml: ^0.14.0
flutter:
assets:
- assets/translations/
```
* **Custom AssetLoader 구현 (`lib/core/i18n/toml_asset_loader.dart`)**:
```dart
import 'dart:ui';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/services.dart';
import 'package:toml/toml.dart';
class TomlAssetLoader extends AssetLoader {
const TomlAssetLoader();
@override
Future<Map<String, dynamic>> load(String path, Locale locale) async {
final assetPath = '$path/${locale.languageCode}.toml';
try {
// 1. Asset 파일 읽기
final String content = await rootBundle.loadString(assetPath);
// 2. TOML 파싱
final TomlDocument document = TomlDocument.parse(content);
// 3. Map으로 변환하여 반환
return document.toMap();
} catch (e) {
// 로깅 또는 빈 맵 반환
print('Error loading TOML asset: $assetPath, error: $e');
return {};
}
}
}
```
* **초기화 (`main.dart`)**:
```dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();
runApp(
EasyLocalization(
supportedLocales: const [Locale('en'), Locale('ko')],
path: 'assets/translations',
fallbackLocale: const Locale('en'),
assetLoader: const TomlAssetLoader(), // 커스텀 로더 적용
child: const MyApp(),
),
);
}
```
---
## 6. 작업 체크리스트
1. [ ] **공통**: `locales/template.toml` 정의 및 초기 키셋 구성.
2. [ ] **CI**: `template.toml` vs `*.toml` 키 동기화 검증 스크립트 작성 (`scripts/verify-i18n.js` or `py`).
3. [ ] **Admin/DevFront**: Vite TOML 플러그인 설정 및 `react-i18next` 연동.
4. [ ] **UserFront**: TOML -> JSON 변환 스크립트 추가 및 `easy_localization` 연동.
### 6.1 UserFront 번역 수정 체크포인트
UserFront 번역을 수정할 때는 아래 순서를 기본 절차로 사용합니다.
1. `locales/*.toml` 수정
2. `./scripts/sync_userfront_locales.sh` 실행
3. UserFront 회귀 테스트 실행
- 예: `cd userfront && flutter test test/english_locale_placeholder_test.dart`
4. 전체 키 정합성 점검
- 예: `node tools/i18n-scanner/index.js`