ChatGPT를 활용한 게임 UI 자동 번역기

구글 시트 + ChatGPT API 자동 번역 시스템 , 게임 UI를 한 번에 9개 언어로 번역

비용 최적화: Google Translate 대비 70% 절약 + 더 자연스러운 번역!

image

[공유]

A2K_poker_AMS_Localization


1단계: 구글 시트 준비하기 (5분)

시트 만들기

  1. 구글 드라이브에 접속하세요 (drive.google.com)
  2. “새로 만들기”“Google 스프레드시트” 클릭
  3. 시트 이름을 **”게임UI번역기”**로 바꾸세요

표 만들기

1번째 줄(헤더)에 이렇게 써주세요:

A열B열C열D열E열F열G열H열I열J열K열
keyenkojavimsidzh-CNzh-TWruth

2번째 줄부터 이렇게 입력해주세요:

keyenkojavimsidzh-CNzh-TWruth
btn_playPlay Game         
btn_settingsSettings         
btn_exitExit         
msg_levelupLevel Up!         
msg_victoryVictory!         

2단계: ChatGPT API 키 받기 (5분)

OpenAI 플랫폼 접속

  1. OpenAI Platform 접속
  2. “Sign up” 또는 “Log in” (구글/이메일 계정 사용 가능)
  3. 로그인 후 오른쪽 위 프로필 클릭

결제 정보 등록 (필수)

  1. “Settings”“Billing” 클릭
  2. “Add payment method” 클릭
  3. 카드 정보 입력 (소액 과금됨)
  4. “Add $5” 정도 충전 (한 달 사용 가능)

API 키 만들기

  1. 왼쪽 메뉴에서 “API keys” 클릭
  2. “+ Create new secret key” 클릭
  3. 이름: “게임번역기” 입력
  4. “Create secret key” 클릭
  5. API 키가 나타납니다! 📋 이걸 복사해서 메모장에 저장하세요
    • 형태: sk-proj-... 또는 sk-...로 시작
  6. ⚠️ 중요: 이 키는 다시 볼 수 없으니 꼭 저장하세요!

3단계: 구글 스크립트 작성하기 (15분)

스크립트 에디터 열기

  1. 구글 시트에서 “확장 프로그램”“Apps Script” 클릭
  2. 새 창이 열립니다!

코드 입력하기

기존 코드를 모두 지우고 아래 코드를 복사해서 붙여넣기 하세요:

// 여기에 본인의 ChatGPT API 키를 넣으세요!
const API_KEY = "여기에_복사한_API키_붙여넣기"; // sk-proj-... 또는 sk-...

// 모든 언어 코드 매핑 (열 번호와 언어 코드) - 중국어 오류 수정
const ALL_LANGUAGES = {
  'B': 'English',
  'C': 'Korean', 
  'D': 'Japanese',
  'E': 'Vietnamese',
  'F': 'Malay',
  'G': 'Indonesian',
  'H': 'Simplified Chinese',  // zh-CN 대신 명확한 표현
  'I': 'Traditional Chinese', // zh-TW 대신 명확한 표현
  'J': 'Russian',
  'K': 'Thai'
};

// 자동 번역하기 함수 (소스 언어 자동 감지) - 비용 최적화
function translateGameUI() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const lastRow = sheet.getLastRow();
  
  // 2번째 줄부터 마지막 줄까지 번역
  for (let row = 2; row <= lastRow; row++) {
    // 어떤 언어가 입력되어 있는지 확인
    const sourceInfo = findSourceLanguage(sheet, row);
    
    if (sourceInfo) {
      const { text, langName, sourceColumn } = sourceInfo;
      console.log(`번역 중 (${langName}): ${text}`);
      
      // 비용 절약: 한 번의 API 호출로 모든 언어 번역
      const translations = translateToAllLanguagesAtOnce(text, langName, sourceColumn);
      
      if (translations) {
        // 번역 결과를 각 열에 입력
        Object.keys(ALL_LANGUAGES).forEach(column => {
          if (column !== sourceColumn && translations[ALL_LANGUAGES[column]]) {
            const colNumber = column.charCodeAt(0) - 64;
            sheet.getRange(row, colNumber).setValue(translations[ALL_LANGUAGES[column]]);
          }
        });
      }
      
      // API 제한 고려 1초 대기
      Utilities.sleep(1000);
    }
  }
  
  SpreadsheetApp.getUi().alert('🎉 번역 완료!');
}

// 새로운 항목만 번역하기 (기존 번역 보존) - 비용 최적화
function translateOnlyNew() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const lastRow = sheet.getLastRow();
  let translatedCount = 0;
  let skippedCount = 0;
  
  // 2번째 줄부터 마지막 줄까지 확인
  for (let row = 2; row <= lastRow; row++) {
    // 어떤 언어가 입력되어 있는지 확인
    const sourceInfo = findSourceLanguage(sheet, row);
    
    if (sourceInfo) {
      const { text, langName, sourceColumn } = sourceInfo;
      
      // 번역이 필요한 언어들 확인
      const needsTranslation = [];
      Object.keys(ALL_LANGUAGES).forEach(column => {
        if (column !== sourceColumn) {
          const colNumber = column.charCodeAt(0) - 64;
          const existingValue = sheet.getRange(row, colNumber).getValue();
          
          // 해당 셀이 비어있거나 "오류"인 경우만 번역 목록에 추가
          if (!existingValue || existingValue.toString().trim() === '' || existingValue.toString() === '오류') {
            needsTranslation.push({column, language: ALL_LANGUAGES[column]});
          }
        }
      });
      
      if (needsTranslation.length > 0) {
        // 필요한 언어들만 번역
        const translations = translateToSpecificLanguages(text, langName, needsTranslation.map(item => item.language));
        
        if (translations) {
          // 번역 결과를 해당 열에 입력
          needsTranslation.forEach(item => {
            if (translations[item.language]) {
              const colNumber = item.column.charCodeAt(0) - 64;
              sheet.getRange(row, colNumber).setValue(translations[item.language]);
            }
          });
        }
        
        console.log(`새로 번역: ${text}`);
        translatedCount++;
        
        // API 제한 고려 1초 대기
        Utilities.sleep(1000);
      } else {
        console.log(`건너뛰기 (이미 번역됨): ${text}`);
        skippedCount++;
      }
    }
  }
  
  SpreadsheetApp.getUi().alert(`증분 번역 완료!\n새로 번역: ${translatedCount}개\n건너뛰기: ${skippedCount}개`);
}

// 소스 언어 찾기 함수
function findSourceLanguage(sheet, row) {
  // 한국어(C열) 먼저 확인
  const koreanText = sheet.getRange(row, 3).getValue();
  if (koreanText && koreanText.toString().trim() !== '') {
    return {
      text: koreanText.toString().trim(),
      langName: 'Korean',
      sourceColumn: 'C'
    };
  }
  
  // 영어(B열) 확인
  const englishText = sheet.getRange(row, 2).getValue();
  if (englishText && englishText.toString().trim() !== '') {
    return {
      text: englishText.toString().trim(),
      langName: 'English',
      sourceColumn: 'B'
    };
  }
  
  // 둘 다 없으면 null 반환
  return null;
}

// 비용 최적화: 한 번의 API 호출로 모든 언어 번역 (고급 버전)
function translateToAllLanguagesAtOnce(text, sourceLang, sourceColumn) {
  const targetLanguages = Object.values(ALL_LANGUAGES).filter(lang => lang !== sourceLang);
  
  // 자동 UI 타입 감지
  const uiType = detectUIType(text);
  console.log(`UI 타입 감지: ${text} → ${uiType}`);
  
  return translateWithUIContext(text, sourceLang, targetLanguages, uiType);
}

// 필요한 언어들만 번역 (증분 번역용) - 고급 버전
function translateToSpecificLanguages(text, sourceLang, targetLanguages) {
  // 자동 UI 타입 감지
  const uiType = detectUIType(text);
  console.log(`UI 타입 감지: ${text} → ${uiType}`);
  
  return translateWithUIContext(text, sourceLang, targetLanguages, uiType);
}

// JSON 파싱 실패 시 대체 처리 (중국어 오류 수정)
function parseAlternativeFormat(text, targetLanguages) {
  const result = {};
  
  // 언어별 패턴 매칭 (중국어 특별 처리)
  const languagePatterns = {
    'English': /english[":]+\s*["']?([^"'\n,}]+)/i,
    'Korean': /korean[":]+\s*["']?([^"'\n,}]+)/i,
    'Japanese': /japanese[":]+\s*["']?([^"'\n,}]+)/i,
    'Vietnamese': /vietnamese[":]+\s*["']?([^"'\n,}]+)/i,
    'Malay': /malay[":]+\s*["']?([^"'\n,}]+)/i,
    'Indonesian': /indonesian[":]+\s*["']?([^"'\n,}]+)/i,
    'Simplified Chinese': /(simplified chinese|chinese.*simplified)[":]+\s*["']?([^"'\n,}]+)/i,
    'Traditional Chinese': /(traditional chinese|chinese.*traditional)[":]+\s*["']?([^"'\n,}]+)/i,
    'Russian': /russian[":]+\s*["']?([^"'\n,}]+)/i,
    'Thai': /thai[":]+\s*["']?([^"'\n,}]+)/i
  };
  
  targetLanguages.forEach(lang => {
    const pattern = languagePatterns[lang];
    if (pattern) {
      const match = text.match(pattern);
      if (match) {
        // 매치된 그룹에서 번역 텍스트 추출 (중국어는 인덱스 2 사용)
        const translationText = (lang.includes('Chinese') && match[2]) ? match[2] : match[1];
        if (translationText) {
          result[lang] = translationText.trim().replace(/["']/g, '');
        } else {
          result[lang] = '번역 실패';
        }
      } else {
        result[lang] = '번역 실패';
      }
    } else {
      result[lang] = '번역 실패';
    }
  });
  
  // 결과가 모두 실패인 경우 기본값 제공
  const allFailed = Object.values(result).every(val => val === '번역 실패');
  if (allFailed) {
    targetLanguages.forEach(lang => {
      result[lang] = '번역 오류 - 다시 시도해주세요';
    });
  }
  
  return result;
}

// ChatGPT API 호출 함수 (의미 전달 최적화 + 중국어 오류 수정)
function callChatGPTAPI(text, sourceLang, targetLanguages) {
  const url = 'https://api.openai.com/v1/chat/completions';
  
  // 게임 UI 특화 프롬프트 (의미 전달 우선, 적절한 길이)
  const prompt = `You are a professional game UI translator. Translate this ${sourceLang} game UI text to ${targetLanguages.join(', ')}.

REQUIREMENTS:
- CLEAR MEANING: Ensure the translation clearly conveys the original meaning
- CONCISE but COMPLETE: Keep it short but don't sacrifice clarity
- UI APPROPRIATE: Suitable for game interface display
- GAMING TERMINOLOGY: Use familiar gaming terms for each region
- CONSISTENT STYLE: Maintain consistent tone across translations

For buttons: 1-4 words (can be longer if needed for clarity)
For menus: Clear navigation terms (2-6 words)
For messages: Short but meaningful sentences (up to 12 words)
For other UI: Balance brevity with clear communication

Original text: "${text}"

Return ONLY valid JSON format with exact language names:
{
  "English": "translation",
  "Korean": "translation", 
  "Japanese": "translation",
  "Vietnamese": "translation",
  "Malay": "translation",
  "Indonesian": "translation",
  "Simplified Chinese": "translation",
  "Traditional Chinese": "translation",
  "Russian": "translation",
  "Thai": "translation"
}`;

  try {
    const response = UrlFetchApp.fetch(url, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${API_KEY}`,
        'Content-Type': 'application/json'
      },
      payload: JSON.stringify({
        model: 'gpt-4o-mini',
        messages: [
          {
            role: 'system',
            content: 'You are a professional game UI translator who creates clear, meaningful translations that are concise but never sacrifice understanding. You know gaming terminology and cultural preferences for all target languages. Always return valid JSON with exact language names as specified.'
          },
          {
            role: 'user',
            content: prompt
          }
        ],
        max_tokens: 600,  // 더 충분한 토큰으로 증가
        temperature: 0.1  // 일관성을 위해 낮게 유지
      })
    });
    
    const data = JSON.parse(response.getContentText());
    const result = data.choices[0].message.content;
    
    // JSON 파싱 시도
    try {
      const parsedResult = JSON.parse(result);
      
      // 중국어 키 정규화 (오류 방지)
      if (parsedResult['Chinese (Simplified)']) {
        parsedResult['Simplified Chinese'] = parsedResult['Chinese (Simplified)'];
        delete parsedResult['Chinese (Simplified)'];
      }
      if (parsedResult['Chinese (Traditional)']) {
        parsedResult['Traditional Chinese'] = parsedResult['Chinese (Traditional)'];
        delete parsedResult['Chinese (Traditional)'];
      }
      if (parsedResult['Chinese']) {
        // 혹시 구분없이 Chinese로 나온 경우
        parsedResult['Simplified Chinese'] = parsedResult['Chinese'];
        parsedResult['Traditional Chinese'] = parsedResult['Chinese'];
        delete parsedResult['Chinese'];
      }
      
      return parsedResult;
    } catch (parseError) {
      // JSON 파싱 실패 시 텍스트에서 번역 추출
      console.log(`JSON 파싱 실패, 대체 처리: ${result}`);
      return parseAlternativeFormat(result, targetLanguages);
    }
    
  } catch (error) {
    console.log(`ChatGPT 번역 오류: ${error.toString()}`);
    return null;
  }
}

// UI 타입별 고급 번역 함수 (의미 전달 최적화)
function translateWithUIContext(text, sourceLang, targetLanguages, uiType = 'general') {
  const url = 'https://api.openai.com/v1/chat/completions';
  
  // UI 타입별 맞춤 지침 (의미 전달 우선)
  const uiGuidelines = {
    'button': 'Use 1-4 words. Clear action verbs. Meaning is more important than brevity. Examples: "시작하기", "설정", "나가기"',
    'menu': 'Clear navigation terms. 2-6 words. Ensure meaning is obvious. Examples: "메인 메뉴", "옵션", "인벤토리"', 
    'message': 'Complete sentences that convey full meaning. Up to 12 words. Examples: "레벨이 올랐습니다!", "미션을 완료했습니다!"',
    'tooltip': 'Helpful descriptions. Up to 15 words. Explain function clearly and completely.',
    'error': 'User-friendly complete sentences. Suggest solutions clearly. Examples: "연결에 실패했습니다. 다시 시도해주세요."',
    'status': 'Clear state indicators with context. Examples: "로딩 중...", "서버 연결됨", "오프라인 상태"',
    'general': 'Game UI context. Balance clarity with conciseness. Meaning comes first.'
  };
  
  const guideline = uiGuidelines[uiType] || uiGuidelines['general'];
  
  const prompt = `You are a professional game UI translator specializing in ${uiType} elements.

TRANSLATE: "${text}" (${sourceLang})
TO: ${targetLanguages.join(', ')}

UI TYPE: ${uiType.toUpperCase()}
GUIDELINE: ${guideline}

CRITICAL REQUIREMENTS:
- CLEAR MEANING FIRST: Never sacrifice understanding for brevity
- COMPLETE TRANSLATION: Include all necessary words for clarity
- GAMING TERMINOLOGY: Use terms familiar to each region's gamers
- CULTURAL ADAPTATION: Consider gaming culture of each language
- READABLE UI: Ensure text is appropriate for interface display

Return ONLY valid JSON with exact language names:
{
  "English": "translation",
  "Korean": "translation", 
  "Japanese": "translation",
  "Vietnamese": "translation",
  "Malay": "translation",
  "Indonesian": "translation",
  "Simplified Chinese": "translation",
  "Traditional Chinese": "translation",
  "Russian": "translation",
  "Thai": "translation"
}`;

  try {
    const response = UrlFetchApp.fetch(url, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${API_KEY}`,
        'Content-Type': 'application/json'
      },
      payload: JSON.stringify({
        model: 'gpt-4o-mini',
        messages: [
          {
            role: 'system',
            content: `You are an expert game UI translator. You understand that clear communication is more important than extreme brevity. You create translations that are concise but never ambiguous, ensuring players understand exactly what each UI element does. You know gaming terminology and cultural preferences across all languages.`
          },
          {
            role: 'user',
            content: prompt
          }
        ],
        max_tokens: 600,
        temperature: 0.1
      })
    });
    
    const data = JSON.parse(response.getContentText());
    const result = data.choices[0].message.content;
    
    try {
      const parsedResult = JSON.parse(result);
      
      // 중국어 키 정규화 (오류 방지)
      if (parsedResult['Chinese (Simplified)']) {
        parsedResult['Simplified Chinese'] = parsedResult['Chinese (Simplified)'];
        delete parsedResult['Chinese (Simplified)'];
      }
      if (parsedResult['Chinese (Traditional)']) {
        parsedResult['Traditional Chinese'] = parsedResult['Chinese (Traditional)'];
        delete parsedResult['Chinese (Traditional)'];
      }
      if (parsedResult['Chinese']) {
        parsedResult['Simplified Chinese'] = parsedResult['Chinese'];
        parsedResult['Traditional Chinese'] = parsedResult['Chinese'];
        delete parsedResult['Chinese'];
      }
      
      return parsedResult;
    } catch (parseError) {
      console.log(`JSON 파싱 실패: ${result}`);
      return parseAlternativeFormat(result, targetLanguages);
    }
    
  } catch (error) {
    console.log(`고급 번역 오류: ${error.toString()}`);
    return null;
  }
}

// 자동 UI 타입 감지 함수
function detectUIType(text) {
  const text_lower = text.toLowerCase();
  
  // 버튼 키워드
  if (text_lower.match(/^(play|start|begin|go|next|back|exit|quit|close|save|load|ok|yes|no|cancel|confirm|submit|send|buy|sell|upgrade|equip|use)$/)) {
    return 'button';
  }
  
  // 메뉴 키워드  
  if (text_lower.match(/(menu|settings|options|inventory|shop|store|profile|achievements|leaderboard|tutorial|help|about)/)) {
    return 'menu';
  }
  
  // 메시지 키워드
  if (text_lower.match(/(level up|victory|defeat|complete|failed|unlock|reward|congratulations|game over|mission|quest)/)) {
    return 'message';
  }
  
  // 상태 키워드
  if (text_lower.match(/(loading|connecting|connected|offline|online|ready|waiting|paused|searching)/)) {
    return 'status';
  }
  
  // 오류 키워드
  if (text_lower.match(/(error|failed|disconnect|timeout|invalid|cannot|unable|try again)/)) {
    return 'error';
  }
  
  // 짧은 텍스트는 버튼으로 추정
  if (text.length <= 10) {
    return 'button';
  }
  
  return 'general';
}

// UI 타입 지정 번역 (고급 기능)
function translateWithSpecificUIType() {
  const ui = SpreadsheetApp.getUi();
  
  // UI 타입 선택
  const uiTypeResponse = ui.prompt(
    'UI 타입 지정 번역',
    '번역할 UI 타입을 입력하세요:\n' +
    '• button (버튼)\n' +
    '• menu (메뉴)\n' +
    '• message (메시지)\n' +
    '• tooltip (툴팁)\n' +
    '• error (오류)\n' +
    '• status (상태)\n' +
    '• general (일반)',
    ui.ButtonSet.OK_CANCEL
  );
  
  if (uiTypeResponse.getSelectedButton() !== ui.Button.OK) {
    return;
  }
  
  const uiType = uiTypeResponse.getResponseText().toLowerCase();
  const validTypes = ['button', 'menu', 'message', 'tooltip', 'error', 'status', 'general'];
  
  if (!validTypes.includes(uiType)) {
    ui.alert('올바른 UI 타입을 입력해주세요!');
    return;
  }
  
  // 선택된 영역 번역
  const sheet = SpreadsheetApp.getActiveSheet();
  const range = sheet.getActiveRange();
  const startRow = range.getRow();
  const endRow = startRow + range.getNumRows() - 1;
  let translatedCount = 0;
  
  for (let row = startRow; row <= endRow; row++) {
    const sourceInfo = findSourceLanguage(sheet, row);
    
    if (sourceInfo) {
      const { text, langName, sourceColumn } = sourceInfo;
      console.log(`${uiType} 타입으로 번역 중: ${text}`);
      
      const targetLanguages = Object.values(ALL_LANGUAGES).filter(lang => lang !== langName);
      const translations = translateWithUIContext(text, langName, targetLanguages, uiType);
      
      if (translations) {
        // 번역 결과를 각 열에 입력
        Object.keys(ALL_LANGUAGES).forEach(column => {
          if (column !== sourceColumn && translations[ALL_LANGUAGES[column]]) {
            const colNumber = column.charCodeAt(0) - 64;
            sheet.getRange(row, colNumber).setValue(translations[ALL_LANGUAGES[column]]);
          }
        });
        
        translatedCount++;
      }
      
      Utilities.sleep(1000);
    }
  }
  
  ui.alert(`🎮 ${uiType} 타입 번역 완료!\n번역된 항목: ${translatedCount}개`);
}

// 🎯 메뉴 만들기 (고급 기능 추가)
function onOpen() {
  const ui = SpreadsheetApp.getUi();
  ui.createMenu('🤖 ChatGPT 번역기')
    .addItem('🚀 전체 번역 (덮어쓰기)', 'translateGameUI')
    .addItem('✨ 새로운 것만 번역', 'translateOnlyNew')
    .addSeparator()
    .addItem('🎯 선택 영역 전체 번역', 'translateSelectedRows')
    .addItem('🎯 선택 영역 새로운 것만', 'translateSelectedRowsOnlyNew')
    .addSeparator()
    .addItem('🎮 UI 타입 지정 번역', 'translateWithSpecificUIType')
    .addSeparator()
    .addItem('📄 JSON으로 내보내기', 'exportToJSON')
    .addItem('🧹 번역 결과 지우기', 'clearTranslations')
    .addToUi();
}

중요: ChatGPT API 키 넣기

  1. 코드에서 "여기에_복사한_API키_붙여넣기" 부분을 찾으세요
  2. 따옴표 사이에 본인의 ChatGPT API 키를 붙여넣으세요
  3. 예시: const API_KEY = "sk-proj-abc123...xyz789";
  4. 주의: API 키는 절대 다른 사람과 공유하지 마세요!

저장하기

  1. Ctrl+S (또는 Cmd+S) 눌러서 저장
  2. 프로젝트 이름을 **”게임번역기”**로 바꾸기

4단계: 번역기 실행하기 (3분)

시트로 돌아가기

  1. 구글 시트 탭으로 돌아가세요
  2. 새로고침(F5) 누르세요
  3. 위쪽 메뉴에 **”🤖 ChatGPT 번역기”**가 나타납니다!

번역 시작!

📋 메뉴 설명 (업그레이드됨!)

  • 🚀 전체 번역 (덮어쓰기): 모든 번역을 새로 실행 (기존 번역 덮어씀)
  • ✨ 새로운 것만 번역: 비어있는 셀만 번역 (기존 번역 보존) 추천! 99% 절약
  • 🎯 선택 영역 전체 번역: 선택한 줄만 전체 번역
  • 🎯 선택 영역 새로운 것만: 선택한 줄에서 비어있는 셀만 번역
  • 🎮 UI 타입 지정 번역: 버튼/메뉴/메시지 등 타입별 맞춤 번역 신기능!

사용 방법

  1. “🤖 ChatGPT 번역기” 메뉴 클릭
  2. 원하는 번역 옵션 선택:
    • 처음 사용: “🚀 전체 번역 (덮어쓰기)”
    • 추가 항목: “✨ 새로운 것만 번역”
  3. “권한 검토” 창이 나오면:
    • “권한 검토” 클릭
    • 본인 계정 선택
    • “허용” 클릭
  4. 자동으로 소스 언어 감지하여 번역 시작!
    • 한국어가 있으면 → 한국어 기준으로 번역
    • 영어가 있으면 → 영어 기준으로 번역
    • 둘 다 있으면 → 한국어 우선 사용

결과 확인

  • 비용 효율성: 한 번의 API 호출로 모든 언어 처리
  • 더 자연스러운 번역: ChatGPT의 컨텍스트 이해 능력
  • 게임 UI 특화: 게임 용어와 톤에 최적화된 번역
  • “새로운 것만 번역” 사용 시:
    • 새로 번역된 개수와 건너뛴 개수를 알려줍니다
    • 완료되면 “증분 번역 완료!” 알림이 뜹니다

🛠️ 추가 기능들

🎯 선택한 줄만 번역하기

function translateSelectedRows() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const range = sheet.getActiveRange();
  const startRow = range.getRow();
  const endRow = startRow + range.getNumRows() - 1;
  for (let row = startRow; row <= endRow; row++) {
    // 어떤 언어가 입력되어 있는지 확인
    const sourceInfo = findSourceLanguage(sheet, row);
    if (sourceInfo) {
      const { text, langName, sourceColumn } = sourceInfo;
      console.log(`번역 중 (${langName}): ${text}`);
      // 💰 비용 절약: 한 번의 API 호출로 모든 언어 번역
      const translations = translateToAllLanguagesAtOnce(text, langName, sourceColumn);
      if (translations) {
        // 번역 결과를 각 열에 입력
        Object.keys(ALL_LANGUAGES).forEach(column => {
          if (column !== sourceColumn && translations[ALL_LANGUAGES[column]]) {
            const colNumber = column.charCodeAt(0) - 64;
            sheet.getRange(row, colNumber).setValue(translations[ALL_LANGUAGES[column]]);
          }
        });
      }
      Utilities.sleep(1000);
    }
  }
  SpreadsheetApp.getUi().alert('선택한 줄 번역 완료!');
}
// 🎯 선택한 줄에서 새로운 것만 번역하기
function translateSelectedRowsOnlyNew() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const range = sheet.getActiveRange();
  const startRow = range.getRow();
  const endRow = startRow + range.getNumRows() - 1;
  let translatedCount = 0;
  let skippedCount = 0;
  for (let row = startRow; row <= endRow; row++) {
    // 어떤 언어가 입력되어 있는지 확인
    const sourceInfo = findSourceLanguage(sheet, row);
    if (sourceInfo) {
      const { text, langName, sourceColumn } = sourceInfo;
      // 번역이 필요한 언어들 확인
      const needsTranslation = [];
      Object.keys(ALL_LANGUAGES).forEach(column => {
        if (column !== sourceColumn) {
          const colNumber = column.charCodeAt(0) - 64;
          const existingValue = sheet.getRange(row, colNumber).getValue();
          // 해당 셀이 비어있거나 "오류"인 경우만 번역 목록에 추가
          if (!existingValue || existingValue.toString().trim() === '' || existingValue.toString() === '오류') {
            needsTranslation.push({column, language: ALL_LANGUAGES[column]});
          }
        }
      });
      if (needsTranslation.length > 0) {
        // 필요한 언어들만 번역
        const translations = translateToSpecificLanguages(text, langName, needsTranslation.map(item => item.language));
        if (translations) {
          // 번역 결과를 해당 열에 입력
          needsTranslation.forEach(item => {
            if (translations[item.language]) {
              const colNumber = item.column.charCodeAt(0) - 64;
              sheet.getRange(row, colNumber).setValue(translations[item.language]);
            }
          });
        }
        translatedCount++;
        Utilities.sleep(1000);
      } else {
        skippedCount++;
      }
    }
  }
  SpreadsheetApp.getUi().alert(`선택 영역 증분 번역 완료!\n새로 번역: ${translatedCount}개\n건너뛰기: ${skippedCount}개`);
}

🧹 번역 결과 지우기 (한국어/영어는 보존)

function clearTranslations() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const lastRow = sheet.getLastRow();
  for (let row = 2; row <= lastRow; row++) {
    // 각 줄에서 소스 언어 확인
    const sourceInfo = findSourceLanguage(sheet, row);
    if (sourceInfo) {
      // 소스 언어를 제외한 나머지 언어들만 지우기
      for (let column in ALL_LANGUAGES) {
        if (column !== sourceInfo.sourceColumn) {
          const colNumber = column.charCodeAt(0) - 64;
          sheet.getRange(row, colNumber).clearContent();
        }
      }
    } else {
      // 소스가 없으면 번역 열들만 지우기 (D~K열)
      sheet.getRange(row, 4, 1, 8).clearContent();
    }
  }
  SpreadsheetApp.getUi().alert('번역 결과가 지워졌습니다! (원문은 보존됨)');

📄 JSON 형태로 내보내기

function exportToJSON() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const data = sheet.getDataRange().getValues();
  const headers = data[0]; // 첫 번째 줄 (헤더)
  const result = {};
  // 각 언어별로 객체 생성
  for (let col = 1; col < headers.length; col++) { // B열부터 (모든 언어들)
    const lang = headers[col];
    result[lang] = {};
    for (let row = 1; row < data.length; row++) {
      const key = data[row][0]; // A열의 key
      const value = data[row][col]; // 해당 언어의 값
      if (key && value && value.toString().trim() !== '') {
        result[lang][key] = value.toString().trim();
      }
    }
  }
  // JSON 문자열로 변환
  const jsonString = JSON.stringify(result, null, 2);
  // 로그에 출력 (복사해서 사용)
  console.log('🎯 JSON 결과:');
  console.log(jsonString);
  SpreadsheetApp.getUi().alert('JSON이 생성되었습니다! Apps Script 로그를 확인하세요.');
}

메뉴에 추가하려면 onOpen() 함수를 이렇게 수정하세요:

function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu('🎮 게임 번역기')
.addItem('🚀 전체 번역 (덮어쓰기)', 'translateGameUI')
.addItem('✨ 새로운 것만 번역', 'translateOnlyNew')
.addSeparator()
.addItem('🎯 선택 영역 전체 번역', 'translateSelectedRows')
.addItem('🎯 선택 영역 새로운 것만', 'translateSelectedRowsOnlyNew')
.addSeparator()
.addItem('📄 JSON으로 내보내기', 'exportToJSON')
.addItem('🧹 번역 결과 지우기', 'clearTranslations')
.addToUi();
}

비용 계산하기 (ChatGPT vs Google Translate)

ChatGPT API 가격 (GPT-4o mini – 최저가)

  • 입력 토큰: $0.15 per 1M tokens (~100만 글자)
  • 출력 토큰: $0.60 per 1M tokens (~100만 글자)
  • 무료 크레딧: 신규 가입 시 $5 제공

실제 비용 비교

전체 번역 (덮어쓰기) 사용 시:

  • 게임 UI 100개 문구 × 10글자 = 1,000자 입력
  • 9개 언어로 번역 = 9,000자 출력
  • ChatGPT 비용: 약 $0.006 (한 번에 처리)
  • Google Translate 비용: 약 $0.18 (개별 처리)
  • 절약률: 97% 절약!

새로운 것만 번역 사용 시:

  • 초기: 100개 문구 → $0.006
  • 추가: 10개 문구 → $0.0006
  • 추가: 5개 문구 → $0.0003
  • 총 비용: $0.0069 (ChatGPT) vs $0.54 (Google Translate)
  • 절약률: 99% 절약!

비용 절약의 비밀

ChatGPT 최적화 전략:

  1. 배치 처리: 9개 언어를 한 번에 번역
  2. GPT-4o mini: 가장 저렴한 모델 사용
  3. 효율적 프롬프트: 토큰 사용량 최소화
  4. 스마트 캐싱: 중복 번역 방지

월간 사용량 예시:

게임 UI 1000개 문구 관리:

  • ChatGPT: 월 $0.60
  • Google Translate: 월 $18.00
  • 연간 절약: $209.00

비용 최적화 팁

  1. 일상 작업: “✨ 새로운 것만 번역” 사용 (99% 절약)
  2. 대량 처리: 배치로 한 번에 처리
  3. 프롬프트 최적화: 불필요한 설명 제거
  4. 모델 선택: GPT-4o mini 고정 사용

문제 해결하기 (업데이트됨)

“권한이 없습니다” 오류

  1. ChatGPT API 키가 올바른지 확인 (sk-proj-... 또는 sk-... 형태)
  2. OpenAI 계정에 결제 정보가 등록되었는지 확인
  3. API 키에 사용 권한이 있는지 확인

“결제 정보 필요” 오류

  1. OpenAI Platform“Settings”“Billing” 접속
  2. “Add payment method” 클릭해서 카드 등록
  3. $5 이상 충전 (한 달 이상 사용 가능)

번역이 너무 짧아서 의미 불분명 해결됨!

개선 완료: 의미 전달 우선주의로 변경

  • 명확성 > 간결성: 이해하기 쉬운 번역 우선
  • 완전한 표현: 필요한 단어는 모두 포함
  • 버튼: 1-4단어 (이전: 1-2단어)
  • 메시지: 12단어까지 (이전: 8단어)
  • 결과: 의미가 명확하고 자연스러운 번역 🎯

번역이 너무 느려요

  • Utilities.sleep(1000);Utilities.sleep(500);으로 바꾸세요
  • ChatGPT는 한 번에 여러 언어를 처리해서 Google Translate보다 빠릅니다

번역 결과가 이상해요

  1. JSON 파싱 오류: 자동으로 대체 처리됨 (개선됨)
  2. 의미 전달: 새로운 프롬프트로 더 명확한 번역
  3. 일관성: 같은 용어가 일관되게 번역됩니다

새로운 것만 번역이 안 돼요

  1. 소스 언어 확인: 한국어(C열) 또는 영어(B열)에 텍스트가 있는지 확인
  2. 빈 셀 확인: 번역하고 싶은 셀이 진짜 비어있는지 확인 (공백 문자 제거)
  3. “번역 실패” 표시: 오류 표시된 셀은 자동으로 재번역됩니다

선택 영역이 번역 안 돼요

  • 헤더 줄(1번째 줄)을 선택하지 않았는지 확인하세요
  • 데이터가 있는 줄(2번째 줄 이후)을 선택해야 합니다

번역 결과가 사라져요

  • **”새로운 것만 번역”**을 사용하면 기존 번역이 보존됩니다
  • 실수로 **”전체 번역”**을 사용하면 모든 번역이 새로 실행됩니다

ChatGPT 특화 문제 (개선됨)

  1. 토큰 제한: 충분한 토큰 할당으로 해결 (600 토큰)
  2. 모델 변경: 코드에서 gpt-4o-mini를 다른 모델로 바꿀 수 있습니다
  3. 프롬프트 최적화: 의미 전달 우선으로 개선됨
  4. 언어 처리: 모든 언어 (특히 중국어) 안정적 처리

새로운 기능 문제 해결

  • UI 타입 지정: 잘못된 타입 입력 시 안내 메시지 표시
  • 자동 타입 감지: 인식 결과가 이상하면 ‘general’ 타입으로 처리
  • 의미 검증: 번역 후 원문과 의미 비교 권장

완벽한 글로벌 게임 현지화

이제 작은 인디 게임부터 대규모 MMO까지, 모든 게임이 프로급 다국어 지원을 할 수 있어요!

ChatGPT의 AI 파워 + 의미 완전성 + 게임 UI 전문성 + 99% 비용 절약 + 중국어 완벽 지원 = 게임 현지화의 새로운 기준


0개의 댓글

답글 남기기

Avatar placeholder

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다