번역 팩(Translation Pack) — AI 번역이 쓰는 표준 용어
AI 번역은 매번 같은 표기를 써야 한다. 그 보장을 위해 용어집(원장)이 승인한 표준 번역을 번역 팩으로 내보내고, 번역 파이프라인이 그 팩을 프롬프트에 그대로 넣는다. 번역 프롬프트 조립은 이미 GuideCenter translationService.ts 의 buildTranslationPrompt 가 갖고 있고, 이 계약은 그 입력(glossary)을 어디서 받는가를 정한다.
엔드포인트
GET /api/directory/v1/glossary/pack?sourceLocale=ko&targetLocale=en[&scope=platform,process-center]
Authorization: Bearer <포털 API Key> # 스코프: glossary:read| 파라미터 | 필수 | 설명 |
|---|---|---|
sourceLocale | ✅ | 원문 언어 코드(예: ko) |
targetLocale | ✅ | 번역 언어 코드(예: en) |
scope | — | 쉼표 목록. 생략하면 platform + 호출 포털 (포털이 자기 업무 용어와 플랫폼 표준 단어를 함께 받는다) |
응답
json
{
"data": {
"packVersion": "a3f1…(32hex)",
"generatedAt": "2026-10-11T07:20:00.000Z",
"sourceLocale": "ko",
"targetLocale": "en",
"terms": [
{ "sourceTerm": "결재", "preferredTranslation": "approval", "acronym": null,
"description": "결재 정의", "aliases": ["승인"], "scope": "process-center" }
]
},
"meta": { "generatedAt": "…", "scopes": ["platform", "process-center"] }
}ETag="<packVersion>".If-None-Match로 보내면 바뀌지 않았을 때 304 를 돌려준다(소비자 캐시용).Cache-Control: private, max-age=0, must-revalidate.
규칙 (왜 이렇게 하는가)
| 규칙 | 이유 |
|---|---|
승인(approved)·활성 용어만 담는다 | 초안까지 넣으면 같은 본문이 번역할 때마다 달라진다 — 용어집을 쓰는 의미가 사라진다 |
| 개념 ID 가 없는 번역 전용 용어도 포함한다 | 번역에는 개념이 필요 없다. 온톨로지 팩(/ontology/pack)과 다른 점 |
| 같은 표기가 여러 scope 에 있으면 먼저 온 행(포털 우선) 을 남긴다 | 원장 정렬이 priority 높은 순이라, 포털이 자기 용어를 우선하고 없으면 플랫폼 표준 단어를 쓴다 |
packVersion 은 용어 집합의 지문 | 소비자가 "언제 다시 받아야 하는지" 를 판단할 수 있다(용어 추가·수정·비활성 시 값이 바뀐다) |
| 표준 번역이 빈 행은 버린다 | 번역에 쓸 값이 없다 |
중앙 번역 서비스 (POST /api/directory/v1/translation)
포털마다 번역기를 따로 만들면 같은 용어를 다르게 옮기고 프롬프트 규칙도 갈라진다. 그래서 번역 실행도 콘솔이 제공한다 — 포털은 자기 콘텐츠를 넘기고, 콘솔이 용어 팩을 붙여 프롬프트를 만들고 ModelGateway 로 보낸다. 작업 큐·버전·발행·결과 저장은 포털 소관이다(중앙 서비스는 상태를 갖지 않는다).
POST /api/directory/v1/translation # 스코프: translation:invoke
{
"sourceLocale": "ko", "targetLocale": "en",
"title": "제목", "summary": "요약", "htmlBody": "<p>본문</p>",
"scope": ["platform", "guide-center"], # 생략하면 platform + 호출 포털
"model": "선택"
}
→ { "data": { "title": "Title", "summary": "Summary", "htmlBody": "<p>Body</p>",
"packVersion": "…", "glossaryTermCount": 42, "model": "gpt-x",
"usage": { "promptTokens": 1200, "completionTokens": 800 } },
"meta": { "scopes": ["platform", "guide-center"], "sourceLocale": "ko", "targetLocale": "en" } }- 프롬프트 규칙은 GuideCenter 번역기와 같은 문장을 쓴다(HTML 구조·링크·코드·숫자·placeholder·제품명·ID 보존, 용어집 우선, JSON 응답). 포털이 바뀌어도 모델이 같은 지시를 받는다.
- 모델 호출은
temperature: 0·x-purpose: translation— 같은 입력에 같은 표기, 그리고 사용 귀속. - 필요한 환경변수(다른 포털과 동일):
MODEL_GATEWAY_URL,MODEL_GATEWAY_SERVICE_TOKEN. 없으면 503translation_unavailable로 거절한다 — 조용히 빈 결과나 원문을 돌려주지 않는다. - 팩과 마찬가지로 원장 승인 용어만 쓰고, 용어를 추가하면 모든 포털의 다음 번역에 즉시 반영된다.
소비자
| 소비자 | 쓰는 법 |
|---|---|
| GuideCenter 번역 | server/termLinkDirectory.ts fetchCentralTranslationPack() → server/translationGlossary.ts resolveTranslationGlossary() 가 원장 용어를 먼저, 로컬 용어를 보조로 병합해 TranslationRequest.glossary 에 넣는다. 원장이 없거나 닿지 않으면 로컬로 폴백한다(번역은 멈추지 않는다) |
| (예정) 중앙 번역 서비스 | 포털이 늘어나면 번역 실행 자체를 콘솔로 올린다 — 팩 조회·프롬프트 조립·ModelGateway 호출을 한 곳에서. 공용 라이브러리(@hohyun94/portal-ai)의 번역 모듈 승격이 선행 조건이다(현재 vendored tarball 이라 소스 저장소가 필요) |
배포 env — 어느 앱에 넣나
번역은 콘솔이 실행하고 포털은 콘텐츠만 넘긴다. 그래서 필요한 값도 두 앱에 나뉜다.
| 앱 | env | 값 |
|---|---|---|
| PortalConsole (원장·번역 서비스) | MODEL_GATEWAY_URL + MODEL_GATEWAY_SERVICE_TOKEN | 게이트웨이 주소와 게이트웨이의 같은 이름 값(소비자 토큰). 관리 토큰(MODEL_GATEWAY_ADMIN_TOKEN)과 분리한다 |
| PortalConsole | PORTAL_API_KEYS_JSON | 포털 키 새 항목 — portalId(등록된 포털) + apiKey(48자 hex) + 스코프 glossary:read·glossary:write·translation:invoke |
| GuideCenter (번역 소비자) | DIRECTORY_BASE_URL(또는 PORTAL_CONSOLE_ORIGIN) + DIRECTORY_PORTAL_API_KEY(또는 PORTAL_CONSOLE_API_KEY) | 위 키와 같은 값이어도 되고, 별도 키면 translation:invoke 스코프가 있어야 한다(없으면 403) |
| GuideCenter | GUIDE_TRANSLATION_PROVIDER (선택) | local 이면 중앙 대신 로컬 모델로 번역(폐쇄망·중앙 장애 시 우회) |
| ModelGateway | — | 추가 없음. 이미 서비스 토큰을 갖고 있고, 콘솔이 보내는 값과 같아야 한다. 공급자·키는 기존 구성 그대로 |
| FileStore·기타 포털 | — | 이 기능에는 불필요(원장/번역을 직접 부르지 않는다) |
- 시드 실행 위치: 배포 콘솔에 네트워크가 닿는 곳이면 어디서든 된다(스크립트는 HTTP 클라이언트다).bash
PORTAL_CONSOLE_ORIGIN=https://<배포 콘솔> PORTAL_CONSOLE_PORTAL_API_KEY=<키> pnpm seed:glossary - 스코프를 바꾸는 정식 경로는 콘솔 관리자 화면이다 — 포털 설정 데스크의 API 키 섹션에서 대상 키의 scope 를 편집하면(
settings.updatePortalApiKey) DB 에 즉시 반영되고 감사 로그(PORTAL_API_KEY_SCOPES_UPDATED)가 남는다. 새 키를 만들 필요가 없다. - ⚠️ 반대로 env(
PORTAL_API_KEYS_JSON)에서 기존 키의 스코프를 고치면 반영되지 않는다 — 부팅 시 env→DB 반영이keyId만 갱신하기 때문이다(keyId는 apiKey 해시에서 나오므로 같은 키는 같은 행). env 만으로 스코프를 바꾸려면 apiKey 가 새 값인 항목을 추가하고 그 키를 쓰는 포털 env 를 교체한다.
시드(팩이 비지 않게)
팩은 승인 용어만 담으므로 원장이 비어 있으면 번역이 용어를 못 받는다. 표준 표기를 파일로 두고 한 번에 넣는다.
bash
node scripts/seed-glossary-terms.mjs --dry-run # 무엇을 넣을지/건너뛸지만 확인(네트워크 없음)
PORTAL_CONSOLE_ORIGIN=https://console.example.com \
PORTAL_CONSOLE_PORTAL_API_KEY=*** pnpm seed:glossary # 실제 등록- 시드 파일:
docs/glossary-seed-terms.json(플랫폼 표준 표기 ko↔en 38개). 다른 파일은--file, 소속 강제는--scope로 지정한다. - 멱등: 먼저 같은 언어쌍의 기존 용어를 조회해 이미 있는 표기는 건너뛴다 — 원장에는 (언어쌍·표기) 유니크 제약이 없어 중복 행이 쌓이면 팩에서 서로 다른 번역으로 갈라진다.
- 포털 API 키에
glossary:write스코프가 필요하다. 등록은 각 포털이 자기 소속으로 자유롭게 하고, 승인(status)은 원장이 관리한다. - 업무 용어는 각 포털이 자기 scope(예:
process-center)로 등록하고, 플랫폼 공통 UI 표기는scope=platform으로 둔다 — 팩의 기본 범위가platform+ 호출 포털이라 어느 쪽이든 번역에 들어온다. - FileStore 관리자 화면의 번역 규칙은
FileStore/docs/UI_TERMS.md가 코드 쪽 기준이고, 표기의 정본은 이 원장이다.
관련 문서: UiPlatform/docs/ONTOLOGY_SCOPE.md(온톨로지 범위·소유·저장), GuideCenter/docs/translation-automation.md(번역 파이프라인).