🌐

GitHub Actions × Codex で翻訳PR自動生成CIを構築した話

に公開

はじめに

株式会社Entaleで長期インターンとしてペアケアの開発に携わっています。ペアケアのグローバル展開に伴い、業務の中で翻訳作業が発生するようになり、その効率化を目的として、GitHub ActionsとCodexを用いたPR自動翻訳の仕組みを実装しました。実装に取り組む中で苦戦した部分があったため、その振り返りとして本記事にまとめました。

やりたいこと

1.PRにpr-translationラベルを付与する
2.Github Actionsが起動
3.PRの差分をもとにCodexが翻訳
4.翻訳内容を新規ブランチにコミット
5.元PRに対して翻訳提案PRを自動作成

まず検証したこと

まずは、翻訳処理を行う前段として、特定のPRにpr-translationのラベルを付与した際に、そのPRをベースとして空コミットを積んだ新規PRが自動で作成されるかを検証しました。

ラベル付与をトリガーにWorkflowを起動する

.github/workflows/pr-translation.yaml
on:
  pull_request:
    types: [labeled]

jobs:
  pr-translation:
    if: github.event.label.name == 'pr-translation'
  • on:Workflowがいつ実行されるか
  • jobs:実際に何を実行するか

なので、今回は
「PRにラベルが付いたら、そのラベルが pr-translation のときだけ、翻訳用PRを作る処理を実行する」
というWorkflowを組みました。

対象PRを元に新規ブランチを自動生成する

.github/workflows/pr-translation.yaml
- name: Create branch from PR
        env:
          PR_NUMBER: ${{ github.event.pull_request.number }}
        run: |
          git fetch origin pull/${PR_NUMBER}/head:original-pr
          git checkout original-pr

          git checkout -b translation/pr-${PR_NUMBER}

ラベルが付与された元PRのHEADを取得し、その状態をもとに新規ブランチを自動で生成しています。

commit,push

.github/workflows/pr-translation.yaml
git commit --allow-empty -m "chore: translation placeholder for PR #${PR_NUMBER}"
git push origin translation/pr-${PR_NUMBER}

新規PR作成

.github/workflows/pr-translation.yaml
- name: Create PR
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          BASE_BRANCH: ${{ github.event.pull_request.head.ref }}
        run: |
          gh pr create \
            --base "$BASE_BRANCH" \
            --head "translation/pr-${PR_NUMBER}" \
            --title "Translate PR #${PR_NUMBER}" \
            --body "PR #${PR_NUMBER} を元にした翻訳用PR(検証)"
  • GitHub Actionsワークフロー内で自動的に生成される、GITHUB_TOKENを使用
  • baseとheadを明記

Codexに翻訳してもらう

ここからは、Workflowの中に Codex を組み込み、実際に翻訳を行う部分について説明します。

npm版Codexでの課題

実装当初は、npmパッケージとして提供されている@openai/codexを利用することを検討していました。
しかし、GitHub Actions 上で利用するにはいくつか課題がありました。

  • 実行環境のセットアップがやや煩雑
  • 権限や安全性の担保を個別に考慮する必要がある

openai/codex-actionを採用

GitHub Actions専用に提供されているopenai/codex-actionを利用する構成に切り替えました。

.github/workflows/pr-translation.yaml
- name: Run Codex
  uses: openai/codex-action@v1
  with:
    openai-api-key: ${{ secrets.OPENAI_KEY }}
    prompt-file: temp-prompt.md
    safety-strategy: drop-sudo
    sandbox: workspace-write
  • CIではAPIキーでログインすればOK
  • prompt-file:でプロンプトファイルを指定
  • 変更を書き込む場合はsandbox: workspace-writeを設定

diffの扱い

PR の翻訳対象は、main(正確にはPRのbaseブランチ)と元ブランチとの差分です。
git diffはこちらで用意するよりもCodexが自身で確認して実行した方が自由度が高いので、prompt ファイルの命令に追記することにしました。

.github/workflows/pr-translation.yaml
- name: Prepare Prompt
        id: prep
        run: |
          BASE_REF="origin/${{ github.event.pull_request.base.ref }}"
          sed "s|__BASE_REF__|$BASE_REF|g" .github/codex/prompts/pr-translation.md > temp-prompt.md

PRのbaseブランチ名を取得し、その値をCodexに渡すプロンプト内の__BASE_REF__に埋め込む形をとりました。

プロンプト設計

今回の仕組みで最も重要だったのは「プロンプト設計」です。

LLMは非常に優秀ですが、そのまま使うと以下のような不具合が発生する可能性があります。

  • 差分外のファイルまで修正する
  • 不要なリファクタリングを始める
  • 既存の翻訳キーを変更する
  • index.ts の import 順を勝手に並び替える
  • 整形だけを大量に行う

CI上で自動実行する以上、こうした挙動は許容できません。
そのため、プロンプトは「お願い」ではなく「仕様書」として設計しました。

本記事では読みやすさを重視して汎用化したプロンプトを紹介していますが、実際の運用では、さらに厳格な制約を設けています。

# PR差分に対するi18n化プロンプト(公開用)

あなたは i18n 移行を支援するアシスタントです。

このPRの差分に含まれる変更のみを対象に、ハードコードされたユーザー向け文字列を国際化してください。

---

## 🔒 スコープ固定(最重要)

1. `git diff __BASE_REF__...HEAD` に含まれる変更行のみを対象とする。
2. 差分に関係のないファイルは変更しない。
3. リファクタ・整形・型修正などは行わない。
4. 翻訳対象が存在しない場合は、何も変更しない。

---

## 🧠 作業前の確認(必須)

変更を適用する前に、以下を実行してください:

1. 差分から翻訳対象となり得る文字列をすべて列挙する。
2. それらが「ユーザー向け表示文言」であることを確認する。
   - UIテキスト
   - ボタン文言
   - エラーメッセージ
   - モーダル文言 など
3. ログ出力・コメント・テストコード内の文字列は対象外とする。

確認後、変更を適用してください。

---

## 🎯 i18n化ルール

1. ハードコードされた文字列を翻訳関数(例:`t("key.path")`)に置き換える。
2. 意味が明確で一貫性のある翻訳キーを設計する。
3. 既存の翻訳キーは変更しない。
4. 既存の辞書構造を維持する。
5. 辞書ファイルが存在しない場合は、プロジェクトの既存規約に従って新規作成する。
6. すべての対応言語に同一キーを追加する。

---

## 🗂 辞書ファイルに関する制約

- 既存のキーを削除・変更しない。
- 既存の並び順や構造を極力維持する。
- 不要な整形やフォーマット変更を行わない。
- 既存ファイルに翻訳対象がない場合、新規辞書作成は行わない。

---

## 🌍 翻訳品質

- UIに適した自然な表現にする。
- 簡潔で一貫したトーンにする。
- 文脈を考慮する。
- プロダクト名は原則変更しない。

---

## ⚠️ 禁止事項

- 差分外ファイルの変更
- 不要なリファクタリング
- コードスタイル変更
- 無関係な最適化
- 既存翻訳キーのリネーム

---

翻訳対象が存在しない場合は、変更を加えず終了してください。
Entale Developers

Discussion