개발관련

Unity 게임 번역기 개발기 #2: 돈을 쓰기로 했다

삽질연구소 소장 2025. 10. 10. 12:00

로컬 모델의 현실

Qwen2.5-7B로 魔法少女ノ魔女裁判 챕터 1을 번역했다.

  • 어색하다
  • 하지만 플레이는 가능하다
  • GPU는 불타고 있다

챕터 2 시작. 문제 발생.

싫어받다 (X)
미움받다 (O)

로컬 7B 모델은 문맥을 이해 못 한다.

무슨 상황에서 나온말인지 구분을 못 한다.

다른 Unity 게임도 하고 싶었다

魔法少女ノ魔女裁判은 Naninovel 엔진이다. 다른 Unity 게임들도 번역하고 싶었다.

일반 Unity 게임은 텍스트가 MonoBehaviour에 직렬화되어 있다. UnityPy로는 안 읽힌다.

AssetsTools.NET이라는 구원자

UABE라는 도구의 핵심 라이브러리. TypeTree로 모든 Unity 버전 지원.

문제: C# DLL이다.

나는 Python을 쓴다.

해결: pythonnet

pip install pythonnet
# .NET을 Python에서 쓴다.
import clr
from System import Activator
from System.Reflection import Assembly

# AssetsTools.NET 로드
assembly = Assembly.LoadFrom("AssetsTools.NET.dll")
manager_type = assembly.GetType("AssetsTools.NET.AssetsManager")
manager = Activator.CreateInstance(manager_type)

# TypeTree DB 로드
manager.LoadClassPackage("classdata.tpk")

성공. 일반 Unity 게임도 텍스트 추출 가능.

Python에서 .NET DLL 호출하는 날이 올 줄이야.

API 모델 조사 (지갑을 열 시간)

로컬 7B로는 한계다. 돈을 쓰기로 했다.

1. Claude API (Anthropic)

  • 장점: 번역 품질 최상
  • 단점: 비싸다
  • 특이사항: Prompt Caching으로 90% 할인

2. GPT-4 (OpenAI)

  • 장점: 번역 품질 좋음
  • 단점: Claude보다 비쌈
  • 특이사항: Caching 없음 (돈 많이 나감)

3. DeepL API

  • 장점: 번역 전문
  • 단점: 커스텀 규칙 적용 어려움
  • 특이사항: "魔法少女" 같은 고유명사 처리 애매

4. Google Translate

  • 장점: 무료
  • 단점: 품질 낮음 (이미 경험함)
  • 특이사항: Ruby 태그 때문에 폭사

Claude API 선택 (Prompt Caching의 힘)

Prompt Caching이 결정적이었다.

system = [{
    "type": "text",
    "text": open("translation_rules_ja_ko.yaml").read(),  # 5000토큰
    "cache_control": {"type": "ephemeral"}  # 5분 TTL
}]

response = client.messages.create(
    model="claude-3-5-sonnet-20241022",
    system=system,
    messages=[{"role": "user", "content": japanese_text}]
)

비용:

  • 첫 호출: $1.00 (전체 토큰)
  • 캐시 히트: $0.10 (90% 절감!)

번역 규칙 5,000토큰을 매번 보낸다. 하지만 캐싱 덕분에 많이 비용 절감이 된다

번역 엔진 추상화

여러 API를 쉽게 바꿀 수 있게 했다.

# core/translation_engines.py

class BaseTranslator(ABC):
    @abstractmethod
    def translate(self, text, source_lang, target_lang):
        pass

class ClaudeTranslator(BaseTranslator):
    def translate(self, text, source_lang, target_lang):
        # Claude API
        pass

class GPT4Translator(BaseTranslator):
    def translate(self, text, source_lang, target_lang):
        # GPT-4 API
        pass

class DeepLTranslator(BaseTranslator):
    def translate(self, text, source_lang, target_lang):
        # DeepL API
        pass

GUI에서 엔진 선택 가능. 드롭다운 하나면 끝.

Translation Memory (중복은 NO)

같은 문장 두 번 번역하면 바보다.

# core/translation_memory.py

import sqlite3
import hashlib

class TranslationMemory:
    def get(self, original_text, language_pair):
        key = hashlib.md5(f"{original_text}:{language_pair}".encode()).hexdigest()
        self.cursor.execute("SELECT translated FROM cache WHERE key=?", (key,))
        result = self.cursor.fetchone()
        return result[0] if result else None

    def save(self, original_text, translated_text, language_pair):
        key = hashlib.md5(f"{original_text}:{language_pair}".encode()).hexdigest()
        self.cursor.execute(
            "INSERT OR REPLACE INTO cache VALUES (?, ?, ?, ?)",
            (key, original_text, translated_text, language_pair)
        )

실제 비용:

  • Prompt Caching: 90% 절감
  • Translation Memory: 80-90% 중복 방지
  • 최종: 원래 비용의 1-2%

지갑이 살았다.

품질 비교 (돈값을 하는가?)

魔法少女ノ魔女裁判 챕터 1을 다시 번역했다.

모델 품질 속도 비용
Qwen 7B 어색함 1.5시간 무료 (전기세 제외)
Claude 자연스러움 5분 $0.50 (캐싱 후)
GPT-4 좋음 7분 $2.50
DeepL 괜찮음 3분 $1.00

Claude 압도적 승리.

챕터 1 번역에 1.5시간 걸리던 게 5분으로 줄었다. GPU 팬도 조용해졌다.

배운 것

  1. 로컬 모델은 한계가 있다
    7B로는 자연스러운 번역 어렵다. 문맥 이해가 약하다.

  2. Prompt Caching은 사기다
    5,000토큰 규칙을 매번 보내도 90% 절감. 이게 기술이다.

  3. Translation Memory는 필수
    같은 문장 두 번 번역하는건 바보짓. 80-90% 추가 절감.

  4. 엔진 추상화의 중요성
    오늘은 Claude, 내일은 GPT-4. 드롭다운 하나로 끝.

  5. 돈을 쓸 때와 아낄 때를 구분하자
    품질이 중요하면 돈 쓰기. 최적화로 90% 아끼기.


다음 편: 코드 2,600줄이라는 재앙