UI i18n 키 추가 컨벤션 (#1286)
ui/src/locales/{en,ko}.json 에 신규 i18n 키 추가 절차 + 회귀 가드.
컨벤션
1. 키 형식 — 도트 표기법
mlPlugins.headers.enabled
drift.actions.delete
modelGrants.dialog.create
- 카멜케이스 컴포넌트명 (예:
mlPlugins,modelGrants) - 의미적 grouping (
headers,actions,dialog,status) - leaf 키 = 의미 명확 (예:
enabled,disabled,view)
2. en.json + ko.json 동시 등록 필수
vitest setup (ui/src/test/setup.ts) 이 lng: "en" 강제 — en.json 누락 시 키 자체가 fallback 으로 렌더링 → screen.getByText("Enabled") 같은 단언 실패.
# 신규 키 추가 시 검증
grep -c '"mlPlugins"' ui/src/locales/en.json # > 0 확인
grep -c '"mlPlugins"' ui/src/locales/ko.json # > 0 확인
3. test 와 매칭 우선
- 컴포넌트 test 가
getByText("Enabled")처럼 literal 단언하면 en.json 의 값과 정확히 매칭 필수 - humanize 만으로는 부족 — test 기대 문자열 확인 후 매핑
#1286 회귀 방지
본 회귀의 근본 원인: ML Plugin/Drift/ModelGrant UI 머지 시 i18n 키 추가 누락 → vitest 78 files 실패.
회귀 가드 (앞으로 추가 권장)
ui/src/test/i18n-coverage.test.ts (신규 follow-up):
import { expect, test } from "vitest";
import en from "@/locales/en.json";
import { glob } from "fast-glob";
test("all t() keys in admin components exist in en.json", async () => {
const files = await glob("src/components/admin/**/*.tsx");
for (const file of files) {
const content = readFileSync(file, "utf-8");
const keys = [...content.matchAll(/t\("([a-zA-Z][\w.]+)"/g)].map(m => m[1]);
for (const key of keys) {
const exists = key.split(".").reduce((o, k) => o?.[k], en as any);
expect(exists, `Missing key ${key} in en.json (from ${file})`).toBeDefined();
}
}
});
트러블슈팅
| 증상 | 원인 | 해결 |
|---|---|---|
vitest Unable to find element with the text: Enabled | en.json 에 키 없음 → fallback = 키 string | 누락 키 추가 |
mlPlugins.status.enabled 문자열이 UI 에 렌더링됨 | en.json mlPlugins.status.enabled 누락 | 컴포넌트 + setup.ts 확인 후 키 추가 |
| ko.json 에만 있고 en.json 누락 | 비대칭 등록 | 둘 다 추가 (test 는 en만) |
관련
- PR #1286
- vitest setup:
ui/src/test/setup.ts - i18n config:
ui/src/lib/i18n.ts - follow-up: i18n coverage test 추가 (별도 PR)