feat: add internationalization message platform
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
# VD-06: Intl과 typed local message catalog
|
||||
|
||||
- 상태: Accepted
|
||||
- 결정일: 2026-07-26
|
||||
- 적용 브랜치: `feature-frontend-i18n-message-formatting-contract`
|
||||
- 재검토: 승인 locale·복수형 문법·번역 추출 workflow가 local catalog 범위를 넘을 때
|
||||
|
||||
## 배경
|
||||
|
||||
공통 셸, route surface, async/form 상태와 디자인 시스템 기본 문구가 JSX와
|
||||
JavaScript에 분산돼 있었다. 날짜는 일부 application mapper에서 고정 locale로
|
||||
가공되어 presentation이 locale을 바꿀 수 없었고, direction·누락 key·보간 실패
|
||||
정책도 없었다. 반면 현재 skeleton에는 번역 관리 서비스, 실제 번역 승인 절차,
|
||||
복잡한 ICU 문법이라는 제품 요구가 아직 없다. 이 단계에서 i18n vendor를 기본
|
||||
번들에 넣으면 소비 프로젝트가 제거하거나 다시 감싸야 할 의존성만 늘어난다.
|
||||
|
||||
## 결정
|
||||
|
||||
1. 표준 `Intl.DateTimeFormat`, `NumberFormat`, `RelativeTimeFormat`,
|
||||
`ListFormat`, `PluralRules`와 typed local catalog를 기본 엔진으로 사용한다.
|
||||
2. `MessageKey`는 한국어 canonical catalog에서 도출하며 영어와 RTL smoke
|
||||
catalog는 `satisfies Record<MessageKey, string>`으로 compile-time parity를
|
||||
강제한다.
|
||||
3. 보간이 필요한 key는 `MessageParameters`에 key별 parameter object를
|
||||
선언한다. 잘못된 key, 누락·초과 parameter는 TypeScript negative fixture가
|
||||
거절한다.
|
||||
4. 기본 locale은 `ko-KR`, fallback locale도 `ko-KR`이다. 알려지지 않은 locale은
|
||||
language fallback 후 `ko-KR`로 정규화한다. 알려지지 않은 key와 누락 보간은
|
||||
raw key나 외부 값을 출력하지 않고 안전한 공통 fallback을 반환한다.
|
||||
5. `en-XA`는 영어 문구를 확장·accent 처리하는 pseudo locale이고 `ar-EG`는
|
||||
RTL 동작 smoke locale이다. 이 두 locale은 실제 제품 번역 완료를 의미하지
|
||||
않는다.
|
||||
6. 날짜 formatter의 기본 timezone은 테스트와 SSR/브라우저 결과가 흔들리지
|
||||
않도록 `UTC`다. 제품 timezone이 필요하면 호출자가 명시한다. invalid
|
||||
date/number/timezone은 `—`를 반환하고 throw하지 않는다.
|
||||
7. locale state는 React inbound concern이다. `LocaleProvider`가 copy,
|
||||
formatter와 `<html lang/dir>`을 제공하며 application/domain은 미리 번역된
|
||||
문자열 대신 의미 값과 timestamp를 반환한다.
|
||||
8. backend `message`, raw HTML, stack과 내부 key를 catalog 입력으로 신뢰하지
|
||||
않는다. transport/application failure kind를 등록된 사용자 message key로
|
||||
매핑한 뒤 presentation이 해석한다.
|
||||
9. key rename은 즉시 제거하지 않고 `MESSAGE_KEY_ALIASES`에 compatibility alias를
|
||||
둔다. alias는 새 호출의 타입에 포함하지 않아 신규 코드는 canonical key만
|
||||
사용한다.
|
||||
10. extraction, ICU rich message, 번역 SaaS 또는 framework adapter가 필요해지면
|
||||
`presentation/i18n` public API 뒤에서 교체한다. vendor type은 feature와
|
||||
design-system public prop으로 노출하지 않는다.
|
||||
|
||||
## 실행 경계
|
||||
|
||||
```text
|
||||
route/form/failure 의미 값
|
||||
-> presentation message key
|
||||
-> LocaleProvider
|
||||
-> typed catalog / Intl formatter
|
||||
-> text node와 accessible name
|
||||
```
|
||||
|
||||
- canonical catalog: `src/presentation/i18n/catalog.ts`
|
||||
- feature contribution: `src/features/*/contracts/*-message-catalog.js`를
|
||||
`src/features/installed-feature-messages.js`에서 조립
|
||||
- key/보간/fallback/alias: `message-contract.ts`
|
||||
- locale-safe value formatting: `formatters.ts`
|
||||
- React composition과 document metadata: `locale-provider.tsx`
|
||||
- public entry: `src/presentation/i18n/index.ts`
|
||||
|
||||
## 검증
|
||||
|
||||
- `check:i18n`은 catalog key와 placeholder parity, common UI의 한국어 literal,
|
||||
backend message JSX 렌더링과 raw HTML 사용을 검사한다.
|
||||
- `check:i18n:fixture`는 세 금지 사례를 실제로 거절해야 성공으로 인정된다.
|
||||
- type negative fixture는 unknown key와 잘못된 parameter shape를 거절한다.
|
||||
- unit test는 fallback, alias, pseudo 확장, direction과 timezone/number/relative/
|
||||
list/plural/select의 결정성을 검증한다.
|
||||
- component test는 document `lang/dir`, RTL Tabs와 direction-aware pagination,
|
||||
Drawer semantics를 검증한다.
|
||||
- Playwright는 320px pseudo reflow와 RTL compact shell/Drawer/focus restore를
|
||||
Chromium, Firefox, WebKit project에서 실행한다.
|
||||
|
||||
## 한계와 재검토 조건
|
||||
|
||||
현재 catalog는 실제 번역 승인, ICU rich text, locale별 plural 문장 전체 조합,
|
||||
메시지 추출/번역 메모리와 서버 locale negotiation을 제공하지 않는다. 다음 중
|
||||
하나가 확인되면 별도 ADR로 엔진을 재평가한다.
|
||||
|
||||
- 세 개 이상의 실제 승인 locale과 번역 담당 workflow
|
||||
- 복수형·성별·select가 한 문장 안에서 중첩되는 제품 copy
|
||||
- server/client extraction, namespace lazy-loading 또는 번역 SaaS 연동
|
||||
- SSR locale negotiation과 hydration 일치가 필요한 rendering mode
|
||||
|
||||
## Rollback
|
||||
|
||||
`ko-KR` catalog가 기존 기본 문구를 보존하므로 provider를 고정 locale adapter로
|
||||
되돌려도 기본 UX를 유지한다. formatter/vendor 교체 시 public `message`,
|
||||
`date`, `number`, `relativeTime`, `list`, `plural`, `select` 계약과 negative
|
||||
fixture는 유지한다. alias는 migration window 종료 근거 없이 제거하지 않는다.
|
||||
Reference in New Issue
Block a user