Skip to content

AI 번역 (범용 · 용어집 우선) ​

작성 시각: 2026-10-11 범위: 포털 공용 번역 기능 — 텍스트박스·웹편집기 본문을 LLM 으로 다른 언어로 옮기고, 승인 용어집을 우선 적용해 별도 사본을 저장한다.

1. 왜 이렇게 하는가 ​

  • 사용자는 프로세스를 코드가 아니라 위치(NODE No.) 와 익숙한 용어로 인식한다. 기계 번역이 회사 용어를 제멋대로 옮기면 문서가 곧바로 틀린 말이 된다.
  • 그래서 번역은 모델의 일반 번역이 아니라 사전이 정한 번역어를 우선한다. 무엇이 옳은지는 공유 용어집(정본: PortalConsole/DirectoryService 원장)이 정한다.
  • 언어별 컬럼(nameEn·descriptionEn)을 손으로 채우던 방식을 대체한다 — 언어를 늘리면 컬럼이 아니라 행이 는다.

2. 구성 (책임 분리) ​

계층위치책임
번역 엔진@hohyun94/portal-ai (packages/ai-core/src/translate.ts)프롬프트 생성(용어집 주입·HTML 마크업 보존), 응답 검증(용어 반영·구조 손실 경고). 모델·포털을 모른다
번역 UI@hohyun94/portal-foundation (packages/ui-react/src/translation.tsx)PortalTranslateControl — "번역 → 미리보기 → 저장" 한 흐름. 서버 호출은 호출부가 소유
번역 테이블ProcessCenter drizzle (translations)어느 포털·엔티티·필드·언어든 한 형식으로 저장. 키는 (content_key, field, locale)
서버server/services/translationService.ts · server/routers/translation.ts승인 용어집 로드 → 엔진 호출 → 저장(upsert) · 조회
화면client/src/components/ProcessManagementPanel.tsx (수정 폼) · ProcessWorkspace.tsx (조회) · DesignQualityPanel.tsx (설계 품질)필드별 번역 컨트롤 · locale 별 본문 선택 · 관리자 화면 NODE No. 표기

3. 데이터 모델 (translations) ​

text
id · contentKey(portalId:entityType:entityId) · field · locale · content · sourceLocale
sourceContentHash(원문 해시 — 낡은 번역 감지) · glossarySnapshot(적용한 용어집 JSON) · status
createdBy · updatedBy · createdAt · updatedAt        -- UNIQUE(contentKey, field, locale)
  • content_key 규약(process-center:process_node:12)은 용어 연결·추출 큐가 쓰는 것과 같다.
  • 마이그레이션: drizzle/0035_translations.sql(MySQL)·drizzle/postgres-migrations/0026_translations.sql(PostgreSQL). 기존 nameEn·descriptionEn 을 한 번 백필한다(멱등).

4. 번역 규칙 (엔진) ​

  1. 용어집을 못 읽어도 번역은 계속한다: 원장 조회가 거부(포털 API Key 에 glossary:read scope 가 없어 HTTP 403 insufficient_scope)되거나 일시 장애면 빈 용어집으로 일반 번역하고 미리보기에 경고를 남긴다(loadGlossarySafely). 사전 문제로 기능 전체가 멈추지 않게 하는 경계다.
    • 고치는 방법: PortalConsole 관리자 → 포털 API Key 에서 ProcessCenter 키의 scope 에 glossary:read 를 추가(또는 키 재발급 후 PORTAL_CONSOLE_API_KEY 갱신).
  2. 용어집 우선: 본문에 실제로 나온 승인 용어만 골라 "이 표기는 반드시 이 번역어" 를 시스템 프롬프트에 주입한다. 응답이 그 번역어를 담았는지 검증하고, 빠졌으면 경고로 남긴다(사람이 미리보기에서 본다).
  3. HTML 보존: 웹편집기 본문은 태그·속성·구조를 그대로 두고 텍스트 노드만 번역한다(href·src·data-asset-id·코드 값 불변). 태그가 크게 줄면 "구조 손실" 경고.
  4. 지시 언어: 한국어 원문은 한국어로, 그 밖은 영어로 지시한다.
  5. 지시 문구는 소스 언어 기준이며, 대상 언어는 프롬프트에 이름으로 들어간다(ko → "한국어", en → "영어", zh → "중국어" …).

5. 저장·조회 규칙 (테이블이 정본) ​

  • 저장: 사람이 미리보기에서 확정한 번역만 저장한다(translation.save → upsert). 재번역하면 덮어쓴다.
    • 미리보기에서 번역문을 직접 고칠 수 있다 — 저장되는 값은 AI 원본이 아니라 사람이 고친 문장이다. 리치 본문(detailContent·manualContent)은 웹편집기로 편집해 원시 HTML 을 손대지 않는다.
    • 함께 남기는 것: 원문 해시(sourceContentHash — 마크업을 지운 텍스트의 sha256 앞 40자)와 적용한 용어집(glossarySnapshot).
  • 조회: 프로세스 목록 응답(process.list)이 번역 테이블에서 언어본을 채운다.
    • nameEn·descriptionEn 응답 필드는 그대로 유지하되 값의 출처가 테이블이 된다(레거시 컬럼은 아직 번역이 없는 행의 폴백).
    • 리치 본문은 detailContentEn·manualContentEn 로 내려주고, 조회 화면이 locale 에 따라 고른다.
    • translationStale: string[] 로 낡은 번역 필드를 함께 내려준다.
  • 수동 입력 중단: 수정 폼의 NAME / EN 입력은 제거했다 — 영어 이름은 번역으로만 만든다.
  • 설명(description): 수정 폼에서 편집하고(DESCRIPTION / KO + 번역 컨트롤), 언어본은 번역으로 만든다. 조회 화면은 설명 칸을 쓰지 않는다(기존 설계).
  • 관리자 화면 어휘: 설계 품질 패널·상위 항목 선택도 코드가 아니라 NODE No. 로 프로세스를 가리킨다(사용자 화면과 같은 어휘).

5-1. 낡은 번역 (원문 변경 감지) ​

  • 저장 때 남긴 원문 해시와 현재 원문 해시가 다르면 그 필드는 낡았다.
  • 화면: 조회 화면은 영어로 볼 때 해당 섹션 제목 옆에 영어 번역 낡음 배지(translation-stale-*)를, 수정 폼은 번역 컨트롤 위에 다시 번역 안내(translate-stale-*)를 띄운다.
  • 노드 저장 직후: translation.stale 로 낡은 필드를 확인해 "원문이 바뀐 영어 번역이 N건 있습니다" 토스트로 알린다. 자동 재번역은 하지 않는다 — 사람이 미리보기로 확인해야 한다.

5-2. 미리보기 캐시 ​

  • 저장하지 않는 미리보기(번역)는 같은 원문 + 대상 언어 + 용어집이면 30분간 결과를 재사용한다(모델 호출 절약).
  • 캐시 키에 용어집 서명이 들어가므로, 용어집이 바뀌면 자동으로 다시 번역한다. clearTranslationCache() 로 일괄 무효화할 수 있다.
  • 같은 본문이 여러 노드에 있으면 일괄 재번역에서도 한 번만 모델을 부른다.

5-3. 낡은 번역 일괄 재번역 (관리자 도구) ​

  • 관리자 화면의 낡은 번역 재번역 버튼(translation.retranslateStale)이 낡은 번역을 한도(기본 20, 최대 50) 안에서 다시 번역해 바로 저장한다.
  • 사람이 미리보기로 확정하는 원칙의 예외가 아니라 관리자 도구다 — 원문만 바뀌고 방치된 번역을 되살린다.
  • 한 건이 실패해도 나머지는 계속 처리하고, 결과는 { processed, failed, skipped } 로 요약해 토스트로 알린다.

6. 다국어 확장 경로 ​

  • 지금은 ko↔en 이다. 중국어·베트남어를 더하려면 ① 대상 언어를 translations.locale 에 행으로 추가(스키마 변경 없음) ② 중앙 용어집 원장에 언어쌍 추가(sourceLocale·targetLocale) ③ SUPPORTED_LOCALES 확장.
  • 테이블·엔진·UI 는 언어를 나열하지 않으므로 스키마 변경 없이 언어가 늘어난다.

7. API ​

라우트권한설명
translation.translate관리자필드 본문을 번역(저장 안 함) — 결과·적용 용어집·경고 반환. 같은 입력은 캐시 재사용
translation.save관리자확정한 번역본 upsert — 원문 텍스트(sourceText)로 해시를 남기고, 적용 용어집을 스냅샷으로 저장
translation.list로그인노드 하나의 번역본 전부
translation.stale로그인원문이 바뀌어 다시 번역해야 하는 필드 목록
translation.retranslateStale관리자낡은 번역 일괄 재번역(한도 1~50) → { processed, failed, skipped }

8. 테스트 ​

  • 엔진: packages/ai-core/src/translate.test.ts — 용어집 선별·주입·검증, HTML 보존/손실 경고, 빈 본문.
  • UI: packages/ui-react/src/translation.contract.test.tsx — 번역→미리보기→저장, 실패 표시, HTML 렌더.
  • 서버: server/translationService.test.ts — 용어집 로드, AI 비활성 폴백, 미리보기 캐시, HTML 필드 지시, 원문 해시(마크업 무관), 저장 시 해시·용어집 스냅샷, 목록 병합·낡음 판정, 낡은 필드 조회.