機能プラグイン料金リソース
言語を変更
リソースCI/CDパイプラインで.po翻訳を自動化する方法

CI/CDパイプラインで.po翻訳を自動化する方法

SimplePoTranslate Team2026年6月9日
CI/CDパイプラインで.po翻訳を自動化する方法

あなたのチームは1日に14回もmainにマージしています。それらのマージのたびに、新しいユーザー向け文字列(ボタンラベル、エラーメッセージ、ツールチップなど)が追加される可能性があります。そして、それらの文字列はすべて英語のみで始まります。リリースプロセスのある段階で、誰かが新しい文字列に気づき、エクスポートし、翻訳してもらい、貼り戻し、再コンパイルする必要があります。その人物がボトルネックとなり、高速でリリースするチームでは、そのボトルネックによってドイツのユーザーが2スプリントの間、英語のプレースホルダーを見ることになります。

この問題を解決するには、CI/CDパイプラインで翻訳を自動化することです。これにより、新しい文字列はそれらを導入したマージと同時に翻訳されるようになります。日常的なケースにおいて、人間の手を介する必要がなくなります。これは今日完全に実現可能ですが、ほとんどのチームが陥る落とし穴、つまり%sプレースホルダーや.po構造を密かに破損させる生のLLM呼び出しを手動で作成することを避けた場合に限ります。このガイドでは、現実的なCIパイプラインパターン、機能するGitHub Actionsのスケルトン、そして役立つパイプラインと壊れたビルドを出荷するパイプラインを分ける設計上の決定(冪等性、新規のみ翻訳、レビューゲート)について説明します。

なぜ手動翻訳がリリースのボトルネックになるのか

「なぜリリース前に翻訳しないのか」という問いへの答えは、タイミングが合わないからです。文字列は開発者によって継続的に追加されますが、翻訳は別の人が別のスケジュールでバッチ処理で行われます。この2つのリズムの間のギャップで、ローカライゼーションの負債が蓄積されます。

2つのリズムの問題

手動のフローを想像してみてください。開発者が__( 'Export to CSV', 'mytextdomain' )を追加してマージします。誰も.potを再生成しません。2週間後、誰かがwp i18n make-potを実行し、40個の新しい未翻訳文字列(一部は休暇で去った開発者によるもの)に気づき、その半分くらいの意図を推測し、翻訳者に.poファイルを送ります。翻訳が戻ってきて、貼り付けられますが、プレースホルダーが無事だったかどうかは分かりません。その間、それらの文字列が英語のまま3つのリリースが出荷されました。

そこにあるすべてのステップは手作業で、バッチ処理され、エラーが発生しやすいものです。CI/CD自動化の目標は、これをマージ時に自動的に実行され、実際に変更されたものだけを翻訳し、何か問題がある場合は明確に失敗するようなものにすることです。これにより、翻訳は定期的な面倒な作業から、パイプラインの見えない部分へと変わります。

パイプラインパターン:抽出、差分、翻訳、コミット

大まかに言えば、自動翻訳ジョブはmainへのマージ時に4つのステップを実行します。テンプレートの再生成、新しいものの検出、それらの文字列のみの翻訳、そして結果のコミットです。全体は冪等であるべきです。文字列の変更がないマージで実行しても、差分もノイズも発生しないはずです。

抽出と差分

最初のステップは抽出です。コードベース内のすべての文字列を反映するように、現在のソースから.potを再生成します。

wp i18n make-pot . languages/myplugin.pot

2番目のステップは差分です。これはパイプライン全体で最も重要な設計上の決定です。すべてのマージで、すべての文字列を再翻訳したくありません。それはAPI呼び出しを無駄にし、人間がすでに修正した文字列を再翻訳するリスクがあり、巨大なレビュー差分を生成します。代わりに、新しい.potを既存の各.poにマージし、現在空になっているエントリ(本当に新しい文字列)のみを翻訳します。

# Merge new template into each locale, preserving existing translations.
# Newly-added strings appear with empty msgstr; --backup=none keeps the tree clean.
for po in languages/*.po; do
    msgmerge --update --backup=none "$po" languages/myplugin.pot
done

msgmergeの後、真新しい文字列のみが空のmsgstrを持ちます。以前に翻訳されたものはすべて手付かずのままです。この特性こそがパイプラインを冪等にし、レビュー差分を小さく、レビュー可能に保つものです。

翻訳とコミット

3番目のステップでは、空のエントリだけを翻訳ステップに送ります。4番目のステップでは、更新された.poをコミットし、.moをコンパイルし、任意のJSONを再生成します。次に、これらすべてをGitHub Actionsに組み込みます。

GitHub Actionsのスケルトン

「ワークフローファイルでは実際にどのように見えるのか」という問いへの答えは、mainへのプッシュ時にトリガーされるジョブです。このジョブは、抽出-差分-翻訳-コミットのサイクルを実行し、結果を直接mainにコミットするのではなく、プルリクエストを開きます。

ワークフローファイル

name: Translate on merge

on:
  push:
    branches: [ main ]

jobs:
  translate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install WP-CLI and gettext
        run: |
          sudo apt-get update && sudo apt-get install -y gettext
          curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
          sudo mv wp-cli.phar /usr/local/bin/wp && sudo chmod +x /usr/local/bin/wp

      - name: Regenerate template and merge into locales
        run: |
          wp i18n make-pot . languages/myplugin.pot
          for po in languages/*.po; do
            msgmerge --update --backup=none "$po" languages/myplugin.pot
          done

      - name: Translate only new (empty) strings
        env:
          TRANSLATE_API_KEY: ${{ secrets.TRANSLATE_API_KEY }}
        run: ./scripts/translate-new-strings.sh languages/

      - name: Compile .mo and JSON
        run: |
          wp i18n make-mo languages/
          wp i18n make-json languages/ --no-purge

      - name: Open pull request with translations
        uses: peter-evans/create-pull-request@v6
        with:
          branch: chore/auto-translations
          title: "chore: auto-translate new strings"
          commit-message: "chore: auto-translate new strings"

真の作業が隠されている場所

translate-new-strings.shステップで実際の翻訳が行われます。そのスクリプトの素朴なバージョンは、各空のエントリを読み込み、LLM APIに一度に1つの文字列を渡し、その応答を貼り付けます。その素朴なバージョンこそがまさに落とし穴であり、その理由を明確にしておく価値があります。

手作業のLLM呼び出しがファイルを壊す理由

簡潔に言えば、生のLLM呼び出しは.po文字列を散文として扱いますが、.po文字列は散文ではありません。これらはプレースホルダー、複数形、およびコンテキストを持つ構造化データであり、チャット補完呼び出しは喜んでそれらを破壊します。

テストでは見逃してしまうプレースホルダーの破損

Deleted %d of %s filesを一般的なチャットエンドポイントに送信すると、Supprimé %d des %s fichiers(問題なし)またはSupprimé %d de % s fichiers(プレースホルダーにスペースが入り込み、sprintf()が実行時にエラーをスローする)が返ってくる可能性があります。複数形のエントリを送信すると、モデルが2つの形式を1つにまとめてしまい、複数形のカテゴリが2つ以上ある言語を壊す可能性があります。<a href="%s">を含む文字列を送信すると、モデルがURLを翻訳したり、タグを削除したりする可能性があります。これらの障害は、各ロケールでレンダリングされた出力を具体的にテストしない限り、テストには現れません。それらは、バグレポートを読み取れないユーザーのために、本番環境で実行時エラーとして現れます。

メンテナンスの落とし穴

プロンプトエンジニアリングや正規表現による後処理でこれに対抗しようとすることはできますし、多くのチームがそうしています。問題は、Gettextエコシステムがすでに解決したすべてのプレースホルダーの境界ケースを再発見しながら、脆い翻訳エンジンをサイドプロジェクトとして維持することになる点です。変数がどのように破損するか、そしてそれを防ぐ方法については、「translating PO files without breaking code variables」で詳しく説明しています。ここでの教訓は直接的に当てはまります。モデルの品質が問題であることはまれで、その周りの構造的な処理が問題なのです。

API駆動型クラウド翻訳をCIに組み込む

ここで、API駆動型の翻訳サービスがtranslate-new-strings.shによる推測作業に取って代わります。手作業でLLM呼び出しと正規表現の後処理を行う代わりに、CIステップは変更された.poを、Gettext構造をすでに理解し、クリーンな出力を返すサービスにアップロードします。パイプラインの形状は、抽出、差分、翻訳、コミットと同一ですが、脆弱な中間ステップは単一のAPI呼び出しになります。

スクリプトを置き換えるAPI呼び出し

SimplePoTranslateはまさにこの目的のために構築されています。これは自動化とCIに適したクラウドAPIを公開しているため、ワークフローの翻訳ステップは、.poを渡し、翻訳されたものを返すリクエストとなり、文字列ごとのループは不要です。そのSyntax Lockingは、%s%1$s{count}、HTML、およびコードトークンを自動的に保持します。前のセクションで述べたバグの全種類は、あなたが保守する正規表現ではなく、エンジンによって処理されます。完全なGettext plural and msgctxtサポートにより、複数形とコンテキストは往復処理後も保持され、チャット補完呼び出しでは保証できません。

冪等性とレビューゲートは依然としてあなたの管理下にある

どのエンジンを使用するかに関わらず、2つの設計上の決定は依然としてあなたの責任です。1つ目は冪等性です。msgmerge後に空のエントリのみを翻訳し続けることで、何も操作しないマージでは差分が発生しないようにします。2つ目はレビューゲートです。上記のスケルトンのように、ジョブが直接mainにコミットするのではなく、プルリクエストを開くようにします。機械翻訳は文字列を迅速に公開するのに優れていますが、マージ前の人間の目による確認は稀なコンテキストのずれを捕捉します。そしてPRは、一般的なケースをブロックすることなく、その確認の機会を提供します。多くのロケールや多くのクライアントサイトを扱うチームは、「the ideal localization workflow for agencies」のこのパターンを認識するでしょう。そこでは、同じ自動化とレビューのパターンが数十のプロジェクトにわたってスケールします。

結論

壊れたビルドを出荷することなく、CI/CDで翻訳を自動化するには、一貫したパターンがあります。マージ時に.potを再生成し、それを各ロケールにmsgmergeして新しい文字列のみを抽出し、それらの文字列だけを翻訳し、プルリクエストのレビューゲートの背後で結果をコミットします。冪等性は差分をきれいに保ち、レビューゲートは稀な悪い翻訳が本番環境に出るのを防ぎます。

正しく行うべき部分は、翻訳ステップそのものです。手作業のLLM呼び出しは、テストでは検出できない方法でプレースホルダーや複数形を破損させ、提供する機能コードよりも翻訳の接着剤を保守するのに多くの時間を費やすことになります。Syntax Lockingを備えたAPI駆動型エンジンは、その種の障害モード全体を排除するため、パイプラインは新しい文字列が導入されたのと同じマージでそれらを翻訳し、英語以外のユーザーが英語のプレースホルダーを見ることはなくなります。

プレースホルダーを壊すことなく、パイプラインで翻訳を自動化する準備はできていますか?SimplePoTranslateを無料で試す — クレジットカードは不要です。無料枠から始め、独自の.poファイルでAPIを検証し、準備ができたらCIに組み込んでください。