ABOUT ME

Unity로 만드는 게임 개발의 즐거움과 배움을 함께 나누는 공간입니다. 실전 코드 예제와 개발 팁, 프로젝트 경험을 공유하며, 창의적인 아이디어와 함께 성장하는 개발자 커뮤니티를 만들어갑니다.

Today
Yesterday
Total
  • [Google Spreadsheet & Apps Script] Gemini API로 번역 및 검수하기
    Unity 2025. 3. 7. 15:05

    개요

    해당 포스팅은 하단의 포스팅의 연장선으로 작성되었다. Localization을 아직 연동시키지 않았다면 다음 포스팅을 참고하기 바란다. 

    https://cgmoon.tistory.com/11

     

    [Unity + Google SpreadSheet] 구글 스프레드 시트 자동 번역 기능 제작 & Unity 연결(Google Sheet Service) (1)

    개요해당 포스팅은 하단의 포스팅의 연장선으로 작성되었다. Localization을 아직 연동시키지 않았다면 다음 포스팅을 참고하기 바란다.  [Unity] Localization을 활용하여 국가별 맞는 언어 적용하기

    cgmoon.tistory.com

     

    이전 포스팅에서 구글 스프레드 시트에 자동 번역 스크립트를 추가하는 작업을 진행했었다. 

    구글에서 제공하는 LanguageApp 라이브러리를 활용하여 간단하게 기계번역을 진행했었다. 

    하지만 조금 더 자연스러운 번역을 원했고, 이에 다양한 방법을 고민해보았다. 

    0. 번역 및 검수 작업 프로세스 고민 과정

    번역과 검수 작업을 진행하기 위한 세가지 방법을 생각해 보았다.

    1. LanguageApp.translate: 기존 코드에 사용한 방법. 기본적으로 제공하는 라이브러리를 사용하여 기계 번역(무료)
    2. Google Cloud Translation Advanced API: 신경망 기반 번역을 활용한 자연스러운 번역이 가능 (유료)
    3. AI API(Gemini / ChatGPT 등등): 인공지능을 활용하여 자연스러운 번역을 진행(무료 / 유료)

    고려사항은 다음과 같았다.

    1. 길고 복잡한 텍스트를 번역하지는 않기 때문에, 고도화된 기능을 쓸 필요는 없다.

    2. 속도가 느리지는 않았으면 한다. 

    3. 현재는 돈을 사용하기에는 부담스럽다.

     

    그래서 선택한 방법은

    • LanguageApp.translate를 써서 1차적으로 번역을 진행한다. 
    • AI API를 써서 번역 검수를 진행하고, 문제가 있으면 수정하여 반환한다.

    여기서 AI API 는 gemini 무료 플랜을 사용하였다. Chatgpt는 사용하려면 토큰을 충전해줘야 되기 때문에,

    1. Gemini API 키 발급받기

    https://aistudio.google.com/

     

    로그인 - Google 계정

    이메일 또는 휴대전화

    accounts.google.com

    해당 링크로 들어가서 로그인하면 다음과 같은 화면이 뜬다. 

    Get API Key를 누르고 개인정보 처리방침에 동의하면, 

    다음과 같은 화면이 팝업된다.

    API 키 만들기를 눌러 키를 생성해주자. 

    생성된 키를 복사하여 안전한 곳에 붙여두자.

     

    2. 기존 Apps Script 수정하기

    이제 기존 Apps Script에서, AI를 활용하여 검수가 가능하도록 코드를  수정해보자. 

    기존 코드는 다음과 같다 

    // 구글 스프레드 시트에 버튼을 만들어주는 함수
    function onOpen() {
      var ui = SpreadsheetApp.getUi();
      ui.createMenu("번역 탭")
        .addItem("자동 번역 실행", "autoTranslateSheet")
        .addToUi();
    }
    //번역을 진행하는 함수
    function autoTranslateSheet() {
      var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
      var range = sheet.getDataRange(); 
      var values = range.getValues();
    
      // 컬럼 인덱스 찾기
      var headers = values[0]; 
      var keyIndex = headers.indexOf("Key");
      var enIndex = headers.indexOf("English(en)");
      var jaIndex = headers.indexOf("Japanese(ja)");
      var koIndex = headers.indexOf("Korean(ko)");
    
      if (keyIndex === -1 || enIndex === -1 || jaIndex === -1 || koIndex === -1) {
        SpreadsheetApp.getUi().alert("'Key', 'English(en)', 'Japanese(ja)', 'Korean(ko)' 컬럼을 찾을 수 없습니다. 헤더를 확인하십쇼.");
        return;
      }
    
      for (var i = 1; i < values.length; i++) {
        var koText = values[i][koIndex]; 
    	// 한국어 탭에 내용이 있는경우
        if (koText) { 
          if (!values[i][enIndex]) { 
          //한국어를 영어로
            values[i][enIndex] = LanguageApp.translate(koText, "ko", "en");
          }
          if (!values[i][jaIndex]) { 
          //한국어를 일본어로
            values[i][jaIndex] = LanguageApp.translate(koText, "ko", "ja");
          }
        }
      }
    
      // 번역된 값 다시 시트에 반영
      range.setValues(values);
      SpreadsheetApp.getUi().alert("✅ 번역 완료!");
    }

     

    자 이제 수정을 진행해보자.

    1. API 키를 변수로 생성해준다. (맨 위에)

    var GEMINI_API_KEY = "API 키를 붙어녛으세요";

    2. Gemini API를 활용한 번역 검수 함수를 작성한다.

    function refineTranslationWithGemini(text, targetLang) {
      if (!text) return "";
    
      var url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateText?key=" + GEMINI_API_KEY;
      var payload = {
        contents: [{
          role: "user",
          parts: [{
            text: `Please refine the following translation to sound more natural in ${targetLang}.
                   This text will be used in a video game UI or system messages, so make sure the translation is appropriate for a gaming context.
                   Adjust the wording to match common game terminology and ensure clarity for players.
                   Keep the response as short and natural as possible, without additional explanation.
                   Only return the corrected translation without formatting or extra comments:
                   "${text}"`
          }]
        }]
      };
    
      var options = {
        method: "post",
        contentType: "application/json",
        payload: JSON.stringify(payload),
        muteHttpExceptions: true
      };
    
      try {
        Logger.log("Gemini API 요청 중: " + text);
        var response = UrlFetchApp.fetch(url, options);
        var json = JSON.parse(response.getContentText());
        var result = json.candidates && json.candidates.length > 0 ? json.candidates[0].content.parts[0].text : text;
        Logger.log("Gemini 응답 완료: " + result);
        return result;
      } catch (error) {
        Logger.log("Gemini API Error: " + error);
        return text; // 오류 발생 시 원문 유지
      }

    해당 함수는 검수를 중점적으로 하는 함수이다. 

     

    text: 기계번역을 통해 변환된 텍스트

    targetLang: "English", "Japanaese"와 같이 어떤 언어로 검수할지 정의

    예시로, key: continue / Korean: 계속하기 로 작성이 되어있고, 
    LanguageApp.translate를 통해 번역된 키워드는 en: continue / ja: 続ける
    그렇다면, 여기서 text는 다음 단어들이 매개변수로 들어간다.
    이에 맞게 refineTranslationWithGemini("continue","English")로 함수를 실행시키게 되는것이다. 

     

    gemini에서 제공하는 url 형식에 맞춰, 프롬프트를 작성해준다. 

    해당 프롬프트의 내용은

    "좀 더 자연스러운 방향으로 수정해봐라. 비디오 게임 UI에 맞는 용어로 수정하고, 잡다한 설명은 붙이지 마라. 맞는 번역만 반환해라"

    처음에는 

    "Please refine the following translation to sound more natural in " + targetLang + ": '" + text + "'"

    다음과 같은 프롬프트로 작성하였는데, 단어만 반환하는 것이 아닌 전체적인 내용을 반환해주어
    프롬프트를 수정해주었다. 

     

    이제 다음 url로 option에 맞게 반환을 요청하고, 진행상황을 로그로 확인할 수 있도록 Logger.log를 작성해주었다.

     

    이제 번역을 진행하는 for문 안에 검수 요청을 넣어주자.

    for (var i = 1; i < values.length; i++) {
        var koText = values[i][koIndex]; // 한국어 원문 가져오기
        if (koText) {
          Logger.log(`처리 중: Row ${i} / ${totalRows}`);
    
          // Step 1: Google 번역 API 사용 (기본 번역)
          var enTranslation = values[i][enIndex] || LanguageApp.translate(koText, "ko", "en");
          var jaTranslation = values[i][jaIndex] || LanguageApp.translate(koText, "ko", "ja");
    
          // Step 2: Gemini API로 검수 요청 (게임 용어 반영)
          values[i][enIndex] = refineTranslationWithGemini(enTranslation, "English");
          values[i][jaIndex] = refineTranslationWithGemini(jaTranslation, "Japanese");
    
          processedRows++;
          //10개 단위로 진행상황 피드백 해주기
          if (processedRows % 10 === 0) {
            SpreadsheetApp.getActiveSpreadsheet().toast(`🔄 ${processedRows} / ${totalRows} 번역 완료...`, "진행 상황", 5);
          }
        }
      }

    기계번역을 진행하고, API로 검수 작업을 진행한다. 

    AI 피드백에서 걸리는 시간을 확인하기 위해, 10개단위로 토스트 메시지를 띄워 너무 오래걸리지는 않는지 확인할 수 있게 한다.

     

    수정된 전체 코드는 다음과 같다

    var GEMINI_API_KEY = "API Key"; // Gemini API 키 입력
    function onOpen() {
      var ui = SpreadsheetApp.getUi();
      ui.createMenu("번역 탭")
        .addItem("자동 번역 실행", "autoTranslateSheet")
        .addToUi();
    }
    function autoTranslateSheet() {
      var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
      var range = sheet.getDataRange();
      var values = range.getValues();
    
      var headers = values[0];
      var keyIndex = headers.indexOf("Key");
      var enIndex = headers.indexOf("English(en)");
      var jaIndex = headers.indexOf("Japanese(ja)");
      var koIndex = headers.indexOf("Korean(ko)");
    
      if (keyIndex === -1 || enIndex === -1 || jaIndex === -1 || koIndex === -1) {
        SpreadsheetApp.getUi().alert("컬럼을 찾을 수 없습니다. 확인해주세요.");
        return;
      }
    
      // 실행 중 메시지 표시
      SpreadsheetApp.getActiveSpreadsheet().toast("번역 및 검수 작업을 시작합니다...", "진행 상황", 5);
    
      var totalRows = values.length - 1;
      var processedRows = 0;
    
      for (var i = 1; i < values.length; i++) {
        var koText = values[i][koIndex]; // 한국어 원문 가져오기
        if (koText) {
          Logger.log(`처리 중: Row ${i} / ${totalRows}`);
    
          // Step 1: Google 번역 API 사용 (기본 번역)
          var enTranslation = values[i][enIndex] || LanguageApp.translate(koText, "ko", "en");
          var jaTranslation = values[i][jaIndex] || LanguageApp.translate(koText, "ko", "ja");
    
          // Step 2: Gemini API로 검수 요청 (게임 용어 반영)
          values[i][enIndex] = refineTranslationWithGemini(enTranslation, "English");
          values[i][jaIndex] = refineTranslationWithGemini(jaTranslation, "Japanese");
    
          processedRows++;
          if (processedRows % 10 === 0) {
            SpreadsheetApp.getActiveSpreadsheet().toast(`🔄 ${processedRows} / ${totalRows} 번역 완료...`, "진행 상황", 5);
          }
        }
      }
    
      // Google Sheets에 업데이트
      range.setValues(values);
      SpreadsheetApp.getActiveSpreadsheet().toast("번역 및 검수 완료!", "완료", 5);
    }
    
    //Gemini API를 활용한 번역 검수 함수 (게임 용어 반영 프롬프트 추가)
    function refineTranslationWithGemini(text, targetLang) {
      if (!text) return "";
    
      var url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateText?key=" + GEMINI_API_KEY;
      var payload = {
        contents: [{
          role: "user",
          parts: [{
            text: `Please refine the following translation to sound more natural in ${targetLang}.
                   This text will be used in a video game UI or system messages, so make sure the translation is appropriate for a gaming context.
                   Adjust the wording to match common game terminology and ensure clarity for players.
                   Keep the response as short and natural as possible, without additional explanation.
                   Only return the corrected translation without formatting or extra comments:
                   "${text}"`
          }]
        }]
      };
    
      var options = {
        method: "post",
        contentType: "application/json",
        payload: JSON.stringify(payload),
        muteHttpExceptions: true
      };
    
      try {
        Logger.log("🔄 Gemini API 요청 중: " + text);
        var response = UrlFetchApp.fetch(url, options);
        var json = JSON.parse(response.getContentText());
        var result = json.candidates && json.candidates.length > 0 ? json.candidates[0].content.parts[0].text : text;
        Logger.log("✅ Gemini 응답 완료: " + result);
        return result;
      } catch (error) {
        Logger.log("❌ Gemini API Error: " + error);
        return text; // 오류 발생 시 원문 유지
      }
    }

     

    이제 이 기능을 실행시켜보자.

    먼저 테이블에 데이터를 입력해주자.

    Key English(en) Japanese(ja) Korean(ko)
    welcome     환영합니다!
    start_game     게임 시작
    exit     게임 종료하기
    edit_farm     농장 꾸미기
    shop     상점
    new_game     새 게임
    continue     계속하기
    load_game     불러오기
    save_game     저장하기
    settings     설정
    audio_settings     오디오 설정
    graphics_settings     그래픽 설정
    language     언어
    help     도움말
    pause     일시정지
    resume     계속하기
    restart     다시 시작하기
    back     뒤로 가기
    confirm     확인
    cancel     취소
    tutorial     튜토리얼
    inventory     인벤토리
    quest     퀘스트
    skills     스킬
    achievements     업적
    level_up     레벨 업!
    mission_complete     미션 완료!
    mission_failed     미션 실패!
    game_over     게임 오버
    victory     승리!
    defeat     패배…
    reward     보상
    collect     수집하기
    claim_reward     보상 받기
    attack     공격
    defend     방어
    special_move     특수 기술
    use_item     아이템 사용
    equip     장착하기
    unequip     해제하기
    upgrade     업그레이드
    daily_bonus     오늘의 보너스
    event     이벤트
    leaderboard     리더보드
    multiplayer     멀티플레이
    reconnect     다시 연결하기

    일단 테이블에 다음과 같이 데이터를 작성해보자.

    그러고 번역탭을 눌러 기능을 실행시켜보자.

    우측 하단에 토스트 메시지가 팝업되며, 진행상황을 확인할 수 있다. 

    완료된 후 테이블을 보면,

    와우! 꽤나 깔끔하게 번역된 것을 확인할 수 있다. 

     

    마무리하며....

    우리는 이제 번역 및 검수작업을 자동으로 진행해주는 프로그램을 완성시켰다.

    이 코드는 당연히 더 발전시킬 수 있는 여지가 있다. 

    텍스트 변동이 감지되면 자동으로 번역을 해주거나

    이상한 부분을 어떻게 수정했는지 띄워주거나.

    각자의 역량을 활용해서 재밌는 기능을 더 추가해 볼 수 있을것이다.

     

    이 포스팅 글이 이상한 번역때문에 곤욕을 치른 사람들에게 도움이 되길 바라며,

    😘즐코(즐거운 코딩 되시길)!

Designed by Tistory.