📝

Unity LocalizationのCSVデータをopenaiとgithub actionsを使って自動翻訳を行う

2025/01/25に公開

はじめに

OSSのデスクトップマスコット「uDesktopMascot」を開発しています。こちらは多言語に対応したリポジトリおよびプロジェクトにしていきたいという思いで開発を行っています。

https://github.com/MidraLab/uDesktopMascot

以下の記事のようにReadMeはOpenAIのgpt-4o-miを使って日本語から多言語に自動翻訳対応を行っています。

https://zenn.dev/midra_lab/articles/5c53ee7bea6f0e

今回は、アプリ内で使用するテキストをOpenAIを使ってGitHub Actionsで自動翻訳対応を行っていきます。

デモ

実際の翻訳処理の差分は以下のようになります。

以下が実際の処理されたコミット差分です
https://github.com/MidraLab/uDesktopMascot/pull/89/commits/ddb25088c155b548d1521b6574d78a926dbd2c6b

上記のデータをLocalization Tablesにcsvからimportした結果です。すべての言語の翻訳結果が埋まっています。

開発環境

unity

  • Unity 6000.0.31f1(IL2CPP)
  • Localization 1.5.4

python

  • openai 1.60.1
  • python 3.11

大まかな処理の流れ

  1. ローカル環境でLocalization TablesでNew Entryで新規登録を行う(keyと日本語のvalue)
  2. CSVにexportする
  3. コミットする(ただし1,2はCSVで作業をすることで省略することはできます.Key Idはルールがあるので、こちらはunity側に従ってくださいテーブルキーの生成)
  4. github actionsでcsvに差分があるときだけjobを実行
  5. job内から翻訳処理のpythonスクリプトの実行
  6. コミット(PRにもコメント返信)

unity側の対応

パッケージを入れる以外には対応は必要ないです.

github actionsの対応

PRに対してcsvがコミットされたときに処理を行いたいため、以下のようなjobを定義します

name: Auto Localization

on:
  pull_request:
    branches:
      - develop
    paths:
      - 'Assets/uDesktopMascot/LocalizationTable/LocalizationTable.csv'

jobs:
  translate:
    runs-on: ubuntu-latest

    steps:
      - name: リポジトリをチェックアウト
        uses: actions/checkout@v4.2.2
        with:
          fetch-depth: 0

      - name: Pythonをセットアップ
        uses: actions/setup-python@v5.3.0
        with:
          python-version: '3.11'

      - name: 依存関係のインストール
        run: |
          pip install openai pandas

      - name: 翻訳スクリプトを実行
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        run: python .github/scripts/translate_localization.py

      - name: 変更をコミット
        run: |
          git config --local user.email "action@github.com"
          git config --local user.name "GitHub Action"

          # 変更をステージしてコミット
          git add Assets/uDesktopMascot/LocalizationTable/LocalizationTable.csv
          if git commit -m "自動翻訳を追加(GitHub Actionsによる)"; then
            echo "Changes committed successfully."

            # リモートの最新の変更を取り込み、リベース
            if git pull --rebase origin ${{ github.head_ref }}; then
              echo "Successfully rebased."
            else
              echo "Rebase failed. Please review the errors."
              exit 1  # エラーが発生した場合、処理を中止
            fi

            # 最後にプッシュ
            git push origin HEAD:${{ github.head_ref }}
          else
            echo "No changes to commit"
          fi

      - name: PRにコメントを追加
        if: success() || failure()
        uses: actions/github-script@v6
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: ${{ github.event.pull_request.number }},
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: "自動翻訳が完了し、ローカライズCSVに反映されました。"
            })

また翻訳処理は、pythondで以下のように行いました

import os
import pandas as pd
from openai import OpenAI
import csv

client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])

# CSVファイルのパス
csv_path = 'Assets/uDesktopMascot/LocalizationTable/LocalizationTable.csv'

# 現在のCSVを読み込み
df = pd.read_csv(csv_path)

# 対象の言語とその表示名を設定(言語名は英語に変更)
target_languages = {
    'English(en)': 'English',
    'French(fr)': 'French',
    'Italian(it)': 'Italian',
    'Korean(ko)': 'Korean'
}

# 翻訳用の関数を定義
def translate_text(text, target_language):
    try:
        response = client.chat.completions.create(
                    model="gpt-4o-mini",
            messages=[
                        {"role": "system", "content": "You are a helpful assistant."},
                        {"role": "user", "content": f"Translate the following Japanese text to {target_language}. Return only the translated text without any additional explanations or notes.\n\n{text}"}
                    ]
        )
        # 翻訳結果を取得
        translation = response.choices[0].message.content.strip()
        return translation
    except Exception as e:
        print(f"翻訳中にエラーが発生しました:{e}")
        return None

# 各行に対して翻訳を実行
for idx, row in df.iterrows():
    japanese_text = row.get('Japanese(ja)', '')
    key = row.get('Key', '')
    
    # 日本語テキストが存在する場合のみ翻訳を実行
    if pd.notnull(japanese_text) and japanese_text.strip() != '':
        print(f"キー '{key}' の翻訳を実行します。")
        for column, target_language in target_languages.items():
            current_translation = row.get(column, '')
            # 翻訳列が存在しない場合、列を追加
            if column not in df.columns:
                df[column] = ''
            # 翻訳が未実行または空欄の場合のみ翻訳を実行
            if pd.isnull(current_translation) or current_translation.strip() == '':
                translation = translate_text(japanese_text, target_language)
                if translation:
                    df.at[idx, column] = translation
                    print(f"{target_language}への翻訳結果:{translation}")
                else:
                    print(f"キー '{key}' の {target_language} への翻訳に失敗しました。")
            else:
                print(f"'{column}' 列には既に翻訳が存在します。翻訳をスキップします。")
    else:
        print(f"キー '{key}' に日本語テキストが存在しないため、翻訳をスキップします。")

# カスタムCSV書き込み関数を定義
# カスタムCSV書き込み関数を定義
def write_custom_csv(df, csv_path):
    columns = df.columns.tolist()
    with open(csv_path, 'w', newline='', encoding='utf-8') as csvfile:
        # ヘッダーを書き込む(ダブルクォーテーションを付けない)
        csvfile.write(','.join(columns) + '\n')

        # データ行を書き込む(全てのフィールドをダブルクォーテーションで囲む。ただし 'Key' と 'Id' 列を除く)
        for idx, row in df.iterrows():
            data_row = []
            for col in columns:
                value = row[col]
                if pd.isnull(value):
                    value = ''
                else:
                    value = str(value).replace('\n', '\\n').replace('\r', '\\r')
                if col in ['Id']:
                    # Id 列はそのまま
                    value = value.replace(',', '\\,')
                    data_row.append(value)
                else:
                    # ダブルクォーテーションで囲み、内部のダブルクォーテーションをエスケープ
                    value = value.replace('"', '""')
                    data_row.append(f'"{value}"')
            csvfile.write(','.join(data_row) + '\n')


# カスタム関数でCSVを保存
write_custom_csv(df, csv_path)
print("翻訳が完了し、CSVファイルが更新されました。")
MidraLab(ミドラボ)

Discussion