# 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> 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`