diff --git a/docs/RELEASE-NOTES.md b/docs/RELEASE-NOTES.md index 66ae927..f4c4c81 100644 --- a/docs/RELEASE-NOTES.md +++ b/docs/RELEASE-NOTES.md @@ -5,6 +5,7 @@ ## [Unreleased] ### 변경 +- **i18n 하드코딩 한글 제거 (alert/confirm/aria-label 우선순위)** — `common.json` 에 `aria` / `error` / `dialog` / `success` / `message` 네임스페이스 추가 (ko/en 대칭, 52개 키). 운영자 노출 `alert('실패: ' + msg)` 11건과 접근성 위반 `aria-label="역할 코드"` 등 40+건을 `t('aria.*')` / `t('error.*')` / `t('dialog.*')` 로 일괄 치환. parent-inference / admin / detection / enforcement / vessel / statistics / ai-operations 전 영역. MainLayout 언어 토글은 `title={t('message.switchToEnglish')}` + `aria-label={t('aria.languageToggle')}` 로 정비 - **iran 백엔드 프록시 잔재 제거** — `IranBackendClient` dead class 삭제, `application.yml` 의 `iran-backend:` 블록 + `AppProperties.IranBackend` inner class 정리. prediction 이 kcgaidb 에 직접 write 하는 현 아키텍처에 맞춰 CLAUDE.md 시스템 구성 다이어그램 최신화. Frontend UI 라벨 `iran 백엔드 (분석)` → `AI 분석 엔진` 로 교체, system-flow manifest `external.iran_backend` 노드는 `status: deprecated` 마킹(노드 ID 안정성 원칙 준수, 1~2 릴리즈 후 삭제 예정) - **백엔드 계층 분리** — AlertController/MasterDataController/AdminStatsController 에서 repository·JdbcTemplate 직접 주입 패턴 제거. `AlertService` · `MasterDataService` · `AdminStatsService` 신규 계층 도입 + `@Transactional(readOnly=true)` 적용. 공통 `RestClientConfig @Configuration` 으로 `predictionRestClient` / `signalBatchRestClient` Bean 통합 → Proxy controller 들의 `@PostConstruct` ad-hoc 생성 제거 - **감사 로그 보강** — `EnforcementService` 의 createRecord / updateRecord / createPlan 에 `@Auditable` 추가 (ENFORCEMENT_CREATE/UPDATE/PLAN_CREATE). `VesselAnalysisGroupService.resolveParent` 에 `PARENT_RESOLVE` 액션 기록. 모든 쓰기 액션이 `auth_audit_log` 에 자동 수집 diff --git a/frontend/src/app/layout/MainLayout.tsx b/frontend/src/app/layout/MainLayout.tsx index 115a90e..2ce6dcb 100644 --- a/frontend/src/app/layout/MainLayout.tsx +++ b/frontend/src/app/layout/MainLayout.tsx @@ -282,8 +282,9 @@ export function MainLayout() { {/* 언어 토글 */} @@ -338,7 +339,7 @@ export function MainLayout() {
setPageSearch(e.target.value)} onKeyDown={(e) => { diff --git a/frontend/src/features/admin/AccessControl.tsx b/frontend/src/features/admin/AccessControl.tsx index a8308dc..c9fd72e 100644 --- a/frontend/src/features/admin/AccessControl.tsx +++ b/frontend/src/features/admin/AccessControl.tsx @@ -94,12 +94,12 @@ export function AccessControl() { }, [tab, loadUsers, loadAudit]); const handleUnlock = async (userId: string, acnt: string) => { - if (!confirm(`계정 ${acnt} 잠금을 해제하시겠습니까?`)) return; + if (!confirm(`${acnt} ${tc('dialog.genericRemove')}`)) return; try { await unlockUser(userId); await loadUsers(); } catch (e: unknown) { - alert('실패: ' + (e instanceof Error ? e.message : 'unknown')); + alert(tc('error.operationFailed', { msg: e instanceof Error ? e.message : 'unknown' })); } }; diff --git a/frontend/src/features/admin/DataHub.tsx b/frontend/src/features/admin/DataHub.tsx index 38a57a4..5b3ba0b 100644 --- a/frontend/src/features/admin/DataHub.tsx +++ b/frontend/src/features/admin/DataHub.tsx @@ -341,6 +341,7 @@ type Tab = 'signal' | 'monitor' | 'collect' | 'load' | 'agents'; export function DataHub() { const { t } = useTranslation('admin'); + const { t: tc } = useTranslation('common'); const [tab, setTab] = useState('signal'); const [selectedDate, setSelectedDate] = useState('2026-04-02'); const [statusFilter, setStatusFilter] = useState<'' | 'ON' | 'OFF'>(''); @@ -442,7 +443,7 @@ export function DataHub() {
setSelectedDate(e.target.value)} diff --git a/frontend/src/features/admin/NoticeManagement.tsx b/frontend/src/features/admin/NoticeManagement.tsx index 8d1a5b0..3017241 100644 --- a/frontend/src/features/admin/NoticeManagement.tsx +++ b/frontend/src/features/admin/NoticeManagement.tsx @@ -74,6 +74,7 @@ const ROLE_OPTIONS = ['ADMIN', 'OPERATOR', 'ANALYST', 'FIELD', 'VIEWER']; export function NoticeManagement() { const { t } = useTranslation('admin'); + const { t: tc } = useTranslation('common'); const { hasPermission } = useAuth(); const canCreate = hasPermission('admin:notices', 'CREATE'); const canUpdate = hasPermission('admin:notices', 'UPDATE'); @@ -265,7 +266,7 @@ export function NoticeManagement() { {editingId ? '알림 수정' : '새 알림 등록'} -
@@ -275,7 +276,7 @@ export function NoticeManagement() {
setForm({ ...form, title: e.target.value })} className="w-full bg-surface-overlay border border-slate-700/50 rounded-lg px-3 py-2 text-[11px] text-heading placeholder:text-hint focus:outline-none focus:border-blue-500/50" @@ -287,7 +288,7 @@ export function NoticeManagement() {