AI 번역·현지화 파이프라인: LLM으로 다국어 서비스 자동화

AI 기술

AI 번역현지화i18nLLM 번역다국어 서비스

이 글은 누구를 위한 것인가

  • i18n JSON 파일을 수동으로 번역하는 개발팀
  • 브랜드 용어가 번역마다 달라져서 일관성이 없는 팀
  • DeepL/Google 번역 품질이 부족해 LLM으로 보완하려는 팀

들어가며

"결제하기" 버튼이 어떤 페이지에서는 "Pay Now", 다른 페이지에서는 "Complete Purchase"가 된다면 브랜드 일관성이 깨진다. LLM은 브랜드 가이드라인과 컨텍스트를 이해하고 일관된 번역을 생성한다.

이 글은 bluebutton.kr의 다국어 서비스 운영 을 참고하여 작성했습니다.


1. 번역 파이프라인 설계

[하이브리드 번역 전략]

1단계: 빠른 번역 (DeepL API)
  일반 문장, 빠른 처리
  비용: $0.00002/문자
  품질: 높음 (일반적)

2단계: LLM 후처리 (Claude)
  브랜드 용어 교정
  문화적 맥락 조정
  마케팅 카피 재창작
  비용: 높음, 선별 적용

3단계: 품질 검증
  역번역으로 의미 보존 확인
  용어 일관성 체크
  금지 표현 검사

[번역 우선순위]
  Critical (즉시): 에러 메시지, 법적 고지
  High (1일 내): UI 텍스트, 제품명
  Medium (1주): 도움말, FAQ
  Low (배치): 블로그, 마케팅 이메일

[지원 언어별 고려사항]
  일본어: 존댓말 레벨 (です/だ体)
  아랍어: RTL, 숫자 형식
  중국어: 간체(대륙) vs 번체(대만/홍콩)
  영어: US vs UK 철자

2. 번역 자동화 구현

import anthropic
import json
from dataclasses import dataclass

client = anthropic.Anthropic()

@dataclass
class TranslationConfig:
    source_lang: str = "ko"
    target_lang: str = "en"
    brand_glossary: dict[str, str] = None  # 브랜드 용어 사전
    tone: str = "professional"  # professional, friendly, formal

GLOSSARY_KO_EN = {
    "결제하기": "Complete Purchase",
    "장바구니": "Cart",
    "위시리스트": "Wishlist",
    "포인트": "Points",
    "쿠폰": "Coupon",
}

async def translate_with_context(
    text: str,
    config: TranslationConfig,
    context: str = "",
) -> str:
    """컨텍스트와 브랜드 용어를 반영한 번역"""
    
    glossary_str = ""
    if config.brand_glossary:
        glossary_str = "\n".join(
            f"  {k} → {v}" for k, v in config.brand_glossary.items()
        )
    
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=2000,
        messages=[{
            "role": "user",
            "content": f"""다음 텍스트를 {config.source_lang}에서 {config.target_lang}로 번역하세요.

원본: {text}

{"맥락: " + context if context else ""}

{"브랜드 용어 사전 (반드시 이 번역 사용):\n" + glossary_str if glossary_str else ""}

톤앤매너: {config.tone}

번역문만 반환하세요. 설명 없이."""
        }]
    )
    
    return response.content[0].text.strip()

async def translate_i18n_json(
    source_file: str,
    config: TranslationConfig,
) -> dict:
    """i18n JSON 파일 전체 번역"""
    
    with open(source_file) as f:
        source = json.load(f)
    
    async def translate_value(key: str, value: str | dict) -> str | dict:
        if isinstance(value, dict):
            result = {}
            for k, v in value.items():
                result[k] = await translate_value(f"{key}.{k}", v)
            return result
        
        # 브랜드 용어 직접 치환
        if config.brand_glossary and value in config.brand_glossary:
            return config.brand_glossary[value]
        
        # 짧은 UI 텍스트는 LLM으로 번역
        if len(value) < 200:
            return await translate_with_context(
                value, config, context=f"UI key: {key}"
            )
        
        return await translate_with_context(value, config)
    
    translated = {}
    for key, value in source.items():
        translated[key] = await translate_value(key, value)
    
    return translated

async def evaluate_translation_quality(
    original: str,
    translation: str,
    source_lang: str,
    target_lang: str,
) -> dict:
    """번역 품질 자동 평가"""
    
    response = client.messages.create(
        model="claude-opus-4-7",
        max_tokens=300,
        messages=[{
            "role": "user",
            "content": f"""번역 품질을 평가하세요.

원본 ({source_lang}): {original}
번역 ({target_lang}): {translation}

JSON으로 평가:
{{
  "accuracy": 1-10,
  "fluency": 1-10,
  "style": 1-10,
  "issues": ["문제점1", "문제점2"],
  "suggested_correction": "더 나은 번역 (있을 경우)"
}}"""
        }]
    )
    
    return json.loads(response.content[0].text)

async def batch_translate_strings(
    strings: list[str],
    config: TranslationConfig,
) -> list[str]:
    """여러 문자열 배치 번역 (토큰 절약)"""
    
    numbered = "\n".join(f"{i+1}. {s}" for i, s in enumerate(strings))
    
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=3000,
        messages=[{
            "role": "user",
            "content": f"""다음 문자열 목록을 {config.source_lang}에서 {config.target_lang}로 번역하세요.
번호 순서를 유지하고, "1. 번역1\n2. 번역2" 형식으로 반환하세요.

{numbered}"""
        }]
    )
    
    lines = response.content[0].text.strip().split("\n")
    result = []
    for line in lines:
        if ". " in line:
            result.append(line.split(". ", 1)[1].strip())
    
    return result

마무리

번역 자동화의 ROI는 명확하다. i18n JSON 500개 키를 수동 번역하면 2-3일, LLM 파이프라인은 10분이다. 브랜드 용어 사전을 먼저 구축하고, 마케팅 카피 같은 고창의성 텍스트는 LLM이 직접 생성하게 하는 것이 기계 번역보다 낫다.