チュートリアル//13 読了時間

Django i18n と AI翻訳:完全セットアップガイド

Eray Gündoğmuş
共有

Django i18n と AI翻訳:完全セットアップガイド

Django には GNU gettext をベースにした成熟した国際化(i18n)フレームワークが付属しています。この基盤を AI による翻訳と組み合わせることで、手動翻訳作業に溺れることなく、個人プロジェクトからマルチロケールの本番アプリケーションまでスケールするワークフローが得られます。

このガイドでは、プロセス全体を解説します:Django の i18n 設定、文字列のマーキング、.po ファイルの管理、AI による翻訳の自動化、そして CI/CD パイプラインへの統合です。

重要なポイント

  • Django の i18n システムは GNU gettext を使用します — 文字列は Python とテンプレートでマーキングされ、.po ファイルに抽出され、実行時に使用するバイナリの .mo ファイルにコンパイルされます。
  • AI 翻訳で .po ファイルの翻訳を自動化できます — 機械翻訳 API が未翻訳エントリを一括処理し、数日かかる手作業を数分に短縮します。
  • 品質レビューのステップが不可欠です — AI が生成した翻訳は、特にユーザー向けコンテンツについては、本番デプロイ前にネイティブスピーカーがレビューする必要があります。
  • CI/CD 統合でループを閉じます — 自動化されたパイプラインが新しい文字列を抽出し、翻訳し、メッセージファイルをコンパイルし、手動介入なしにデプロイできます。
  • Better i18n がマネージドワークフローを提供します — カスタムスクリプトを構築する代わりに、.po ファイルを同期し、翻訳を管理し、1 つのプラットフォームからライフサイクル全体を自動化できます。

Django i18n とは?

Django i18n は、Web アプリケーションを複数の言語に翻訳できる Django 組み込みの国際化フレームワークです。GNU gettext をラップして、文字列の抽出、翻訳ファイル管理、実行時の言語切り替えを提供します — これらすべてが Django のテンプレート、フォーム、URL ルーティングに統合されています。

Django は国際化(i18n)とローカライゼーション(l10n)を分離しています。国際化とは複数の言語をサポートするようにコードを準備するプロセスです。ローカライゼーションとは、特定のロケール向けにコンテンツを実際に翻訳し、フォーマットを適応させるプロセスです。Django は django.utils.translation モジュールと gettext ツールチェーンを通じて両方を処理します。

フレームワークは以下の翻訳をサポートします:

  • ビュー、モデル、フォームの Python 文字列
  • 組み込みテンプレートタグを使用したテンプレートコンテンツ
  • ロケールプレフィックスルーティング用の URL パターン
  • django.utils.formats による日付、時刻、数値、通貨のフォーマット

公式リファレンスは Django 国際化ドキュメント をご覧ください。

国際化のための Django 設定

文字列を翻訳用にマーキングする前に、Django の設定を構成し、必要なミドルウェアをインストールする必要があります。

設定を構成する

settings.py を開き、以下を設定します:

# settings.py

from django.utils.translation import gettext_lazy as _

# アプリケーションのデフォルト言語
LANGUAGE_CODE = "en"

# 国際化フレームワークを有効化
USE_I18N = True

# 日付、数値などのローカライズされたフォーマットを有効化
USE_L10N = True

# タイムゾーン対応の datetime を有効化
USE_TZ = True

# アプリケーションがサポートする言語
LANGUAGES = [
    ("en", _("English")),
    ("es", _("Spanish")),
    ("fr", _("French")),
    ("de", _("German")),
    ("ja", _("Japanese")),
]

# Django が翻訳ファイルを探す場所
LOCALE_PATHS = [
    BASE_DIR / "locale",
]

ミドルウェアスタックに LocaleMiddleware も必要です。SessionMiddleware の後、CommonMiddleware の前に配置する必要があります:

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.locale.LocaleMiddleware",  # ここに配置
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

locale ディレクトリ構造を作成します:

mkdir -p locale/{es,fr,de,ja}/LC_MESSAGES

翻訳用の文字列をマーキングする

Django は翻訳可能な文字列をマーキングするための 2 つの主要な関数を提供します:

  • gettext()_() としてエイリアス)— 実行時に文字列を即座に翻訳します
  • gettext_lazy()_() としてエイリアス)— 文字列がレンダリングされるまで翻訳を遅延させます。モデルフィールドやフォームラベルなどのモジュールレベルのコードに必要です

ビューで(gettext を使用):

from django.utils.translation import gettext as _

def dashboard_view(request):
    welcome_message = _("Welcome to your dashboard")
    context = {
        "title": _("Dashboard"),
        "welcome": welcome_message,
    }
    return render(request, "dashboard.html", context)

モデルで(gettext_lazy を使用):

from django.db import models
from django.utils.translation import gettext_lazy as _

class Article(models.Model):
    title = models.CharField(
        max_length=200,
        verbose_name=_("Title"),
    )
    body = models.TextField(
        verbose_name=_("Body"),
        help_text=_("The main content of the article."),
    )
    created_at = models.DateTimeField(
        auto_now_add=True,
        verbose_name=_("Created at"),
    )

    class Meta:
        verbose_name = _("Article")
        verbose_name_plural = _("Articles")

    def __str__(self):
        return self.title

この区別は重要です:gettext_lazy は文字列が実際に表示されたときに正しい言語に解決される遅延文字列プロキシを返します。モデル定義はインポート時に一度評価されるため、そこで gettext を使うとインポート時にアクティブだった言語が固定されてしまいます。

テンプレート翻訳タグ

Django テンプレートは翻訳タグにアクセスするために {% load i18n %} を使用します:

{% trans %} によるシンプルな文字列翻訳:

{% load i18n %}

<h1>{% trans "Welcome to our site" %}</h1>
<p>{% trans "This content will be translated." %}</p>

変数を含む文字列用の {% blocktrans %} によるブロック翻訳:

{% load i18n %}

{% blocktrans with username=user.username %}
    Hello, {{ username }}! You have new notifications.
{% endblocktrans %}

複数形:

{% load i18n %}

{% blocktrans count counter=item_count %}
    You have {{ counter }} item in your cart.
{% plural %}
    You have {{ counter }} items in your cart.
{% endblocktrans %}

言語コンテキストの設定:

{% load i18n %}

{% get_current_language as LANGUAGE_CODE %}
<html lang="{{ LANGUAGE_CODE }}">

URL の国際化

Django の i18n_patterns 関数は URL パターンにアクティブな言語コードをプレフィックスとして付けます:

# urls.py
from django.conf.urls.i18n import i18n_patterns
from django.urls import path, include

urlpatterns = [
    # ローカライズされていない URL(管理画面、API など)
    path("api/", include("api.urls")),
]

urlpatterns += i18n_patterns(
    path("", include("pages.urls")),
    path("blog/", include("blog.urls")),
    path("accounts/", include("accounts.urls")),
    prefix_default_language=True,
)

この設定で URL は次のようになります:

  • /en/blog/ — 英語ブログ
  • /es/blog/ — スペイン語ブログ
  • /fr/blog/ — フランス語ブログ

Django 組み込みの言語切り替えビューも含めるべきです:

from django.conf.urls.i18n import i18n_patterns
from django.urls import path, include

urlpatterns = [
    path("i18n/", include("django.conf.urls.i18n")),
]

urlpatterns += i18n_patterns(
    # ... あなたのパターン
)

これにより、ユーザーがフォームの POST で言語を切り替えられる {% url 'set_language' %} テンプレートタグが有効になります。

.po ファイルと .mo ファイルの操作

Django の翻訳システムは GNU gettext のファイル形式に依存しています。自動化する前にこのパイプラインを理解することが不可欠です。

Django での gettext の仕組み

ワークフローは 3 つのステップに従います:

  1. 抽出makemessages が Python ファイルとテンプレートをスキャンして翻訳可能な文字列を探し、.po(Portable Object)ファイルに書き込みます。
  2. 翻訳 — 翻訳者(または AI)が .po ファイルの各 msgidmsgstr フィールドを埋めます。
  3. コンパイルcompilemessages.po ファイルを Django が実行時に高速ルックアップのために読み込むバイナリ .mo(Machine Object)ファイルに変換します。

メッセージの抽出

プロジェクトのルートから抽出コマンドを実行します:

# 設定されたすべての言語のメッセージを抽出
python manage.py makemessages --all --no-obsolete

# 特定の言語用に抽出
python manage.py makemessages -l es

# JavaScript 文字列を含める(Django の JS i18n カタログ用)
python manage.py makemessages -d djangojs --all

--no-obsolete フラグはコードに存在しなくなった文字列のエントリを削除し、.po ファイルをクリーンに保ちます。

.po ファイルの構造

抽出後、各ロケールは .po ファイルを取得します:

locale/
├── es/
│   └── LC_MESSAGES/
│       └── django.po
├── fr/
│   └── LC_MESSAGES/
│       └── django.po
└── de/
    └── LC_MESSAGES/
        └── django.po

.po ファイルのエントリは次のようになります:

#: templates/dashboard.html:5
msgid "Welcome to your dashboard"
msgstr ""

#: myapp/models.py:12
msgid "Article"
msgstr ""

#. Translators: This is a button label
#: templates/base.html:42
msgid "Submit"
msgstr ""

各エントリには以下が含まれます:

  • #: コメント — ソースファイルと行の参照
  • msgid — 元の文字列(ソース言語で)
  • msgstr — 翻訳された文字列(翻訳されるまで空)
  • #. コメント — 翻訳者へのメモ(コードの Translators: コメントで追加)

メッセージのコンパイル

翻訳が完了したらコンパイルします:

python manage.py compilemessages

これにより .po ファイルの隣に .mo ファイルが作成されます。Django はこれらのバイナリファイルを起動時に読み込み、高速な翻訳ルックアップを行います。新しい翻訳をコンパイルした後は Django サーバー(またはワーカープロセス)を再起動する必要があります。

Django への AI 翻訳の追加

.po ファイルの手動翻訳は正確ですが遅いです。AI による翻訳は数百のエントリを数秒で処理し、人間のレビュアーが改良できる最初の作業版草稿を提供します。

Python による .po ファイルの解析と翻訳

polib ライブラリは .po ファイルをプログラムで読み書きするためのクリーンな API を提供します:

pip install polib openai

以下は AI 翻訳 API を使用して .po ファイルの未翻訳エントリを翻訳するスクリプトです:

# scripts/translate_po.py

import sys
import polib
from openai import OpenAI

client = OpenAI()  # OPENAI_API_KEY 環境変数を使用

TARGET_LANGUAGES = {
    "es": "Spanish",
    "fr": "French",
    "de": "German",
    "ja": "Japanese",
}


def translate_text(text: str, target_language: str) -> str:
    """AI モデルを使用して単一の文字列を翻訳します。"""
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "system",
                "content": (
                    f"You are a professional translator. Translate the following "
                    f"text to {target_language}. Preserve any Python format strings "
                    f"like %(name)s or {{variable}} exactly as they are. "
                    f"Return only the translated text, nothing else."
                ),
            },
            {"role": "user", "content": text},
        ],
        temperature=0.3,
    )
    return response.choices[0].message.content.strip()


def translate_po_file(po_path: str, lang_code: str) -> int:
    """.po ファイル内のすべての未翻訳エントリを翻訳します。"""
    language_name = TARGET_LANGUAGES.get(lang_code)
    if not language_name:
        print(f"サポートされていない言語コード: {lang_code}")
        return 0

    po = polib.pofile(po_path)
    untranslated = po.untranslated_entries()

    if not untranslated:
        print(f"{po_path} に未翻訳エントリはありません")
        return 0

    print(f"{len(untranslated)} エントリを {language_name} に翻訳中...")

    translated_count = 0
    for entry in untranslated:
        try:
            entry.msgstr = translate_text(entry.msgid, language_name)
            entry.flags.append("fuzzy")  # レビューが必要としてマーク
            translated_count += 1
        except Exception as e:
            print(f"  '{entry.msgid[:50]}...' の翻訳エラー: {e}")

    po.save()
    print(f"{po_path} で {translated_count} エントリを翻訳しました")
    return translated_count


if __name__ == "__main__":
    if len(sys.argv) != 3:
        print("使用法: python translate_po.py <パス/django.po> <言語コード>")
        sys.exit(1)

    translate_po_file(sys.argv[1], sys.argv[2])

実行:

python scripts/translate_po.py locale/es/LC_MESSAGES/django.po es
python scripts/translate_po.py locale/fr/LC_MESSAGES/django.po fr

すべてのロケールへのバッチ翻訳

大規模なプロジェクトでは、バッチスクリプトがすべてのロケールを一度に処理します:

# scripts/translate_all.py

from pathlib import Path
from translate_po import translate_po_file, TARGET_LANGUAGES

LOCALE_DIR = Path("locale")


def translate_all_locales():
    """設定されたすべてのロケールの未翻訳エントリを翻訳します。"""
    total = 0
    for lang_code in TARGET_LANGUAGES:
        po_path = LOCALE_DIR / lang_code / "LC_MESSAGES" / "django.po"
        if po_path.exists():
            count = translate_po_file(str(po_path), lang_code)
            total += count
        else:
            print(f"{po_path} に .po ファイルが見つかりません")

    print(f"\n翻訳合計: {total}")


if __name__ == "__main__":
    translate_all_locales()

品質レビューワークフロー

AI が翻訳したエントリは fuzzy フラグでマークされ、Django(および人間のレビュアー)に翻訳の検証が必要であることを伝えます。これは意図的かつ重要です:

  1. AI が翻訳 — すべての未翻訳の msgstr 値が埋められ、fuzzy としてマークされます
  2. レビュアーが確認 — ネイティブスピーカーが fuzzy エントリを確認し、承認後にフラグを削除します
  3. コンパイル — デフォルトでは、fuzzy でないエントリのみがコンパイルされた .mo ファイルに含まれます

fuzzy エントリをレビューするには:

# ロケールごとの fuzzy エントリ数をカウント
for lang in es fr de ja; do
    count=$(grep -c "^#, fuzzy" locale/$lang/LC_MESSAGES/django.po 2>/dev/null || echo 0)
    echo "$lang: $count fuzzy エントリ"
done

レビューステップには Poedit のような .po ファイルエディタや Web ベースのプラットフォームも使用できます。

Django ローカライゼーション向け CI/CD パイプライン

CI/CD パイプラインで抽出・翻訳・コンパイルのサイクルを自動化することで、翻訳がコードと同期し続けることが保証されます。

GitHub Actions ワークフロー

main へのプッシュのたびに実行される GitHub Actions ワークフローを示します:

# .github/workflows/i18n.yml

name: i18n Translation Pipeline

on:
  push:
    branches: [main]
    paths:
      - "**.py"
      - "**.html"
      - "locale/**"

jobs:
  translate:
    runs-on: ubuntu-latest
    permissions:
      contents: write

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install dependencies
        run: |
          pip install -r requirements.txt
          pip install polib openai

      - name: Install gettext
        run: sudo apt-get install -y gettext

      - name: Extract messages
        run: python manage.py makemessages --all --no-obsolete

      - name: AI-translate new strings
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        run: python scripts/translate_all.py

      - name: Compile messages
        run: python manage.py compilemessages

      - name: Commit translation updates
        run: |
          git config user.name "i18n-bot"
          git config user.email "i18n-bot@users.noreply.github.com"
          git add locale/
          git diff --staged --quiet || git commit -m "chore(i18n): update translations"
          git push

パイプラインの内訳

パイプラインは 4 つのステージに従います:

  1. 抽出makemessages がすべての Python ファイルとテンプレートをスキャンして新しい翻訳可能な文字列を探します
  2. 翻訳 — AI 翻訳スクリプトが未翻訳エントリを埋め、fuzzy としてマークします
  3. コンパイルcompilemessages がバイナリ .mo ファイルを生成します
  4. コミット — 変更が次のデプロイで利用できるようにリポジトリに返されます

本番クリティカルなアプリケーションでは、翻訳ステップとコンパイルステップの間に手動承認ゲートを追加してください。これにより、チームは fuzzy 翻訳が公開される前にレビューする機会が得られます。

ローカル開発用 Pre-commit フック

開発中に翻訳の欠落を検出する pre-commit フックも追加できます:

#!/bin/bash
# .git/hooks/pre-commit

# 未翻訳の文字列を確認
python manage.py makemessages --all --no-obsolete 2>/dev/null

if git diff --name-only locale/ | grep -q ".po$"; then
    echo "警告: 新しい翻訳可能な文字列が検出されました。"
    echo "'python scripts/translate_all.py' を実行して翻訳してください。"
fi

Better i18n と Django の統合

上記のスクリプトは機能しますが、カスタム翻訳コードの管理、API キーの管理、レートリミットの処理、レビューワークフローのゼロからの構築が必要です。Better i18n は Django の翻訳ライフサイクル全体を処理するマネージドプラットフォームを提供します。

Better i18n が Django プロジェクトにどのように適合するか:

1. .po ファイルを Better i18n と同期する

カスタム解析スクリプトを書く代わりに、.po ファイルを直接 Better i18n と同期できます。プラットフォームは gettext 形式をネイティブに読み取り、各 msgid/msgstr ペアを翻訳キーにマッピングします。

2. レビューワークフロー付きの AI 翻訳

Better i18n はソフトウェアローカライゼーション専用にチューニングされた組み込み AI 翻訳を提供します。翻訳はマネージドレビューワークフローを経て、チームメンバーが提案を承認、編集、または拒否できます — カスタムツールは不要です。

3. 翻訳の公開と取得

翻訳が承認されたら、更新された .po ファイルとして Django プロジェクトに取り込めます。公開ステップにより、レビュー済みの翻訳のみがコードベースに届くことが保証されます。

4. CI/CD 統合

Better i18n の CLI は CI パイプラインのカスタムスクリプトを置き換えられます。同期・翻訳・取得のサイクルが GitHub Actions ワークフロー内の単一コマンドになります。

Django の i18n 機能の詳細については、Django i18n フレームワークガイドをご覧ください。ワークフロー向けの翻訳ツールを評価しているなら、AI 翻訳ツールガイドが現在の状況を網羅しています。

よくある質問

Django における i18n と l10n の違いは何ですか?

国際化(i18n)は Django アプリケーションを翻訳可能にするプロセスです — 文字列のマーキング、ミドルウェアの設定、URL パターンのセットアップ。ローカライゼーション(l10n)は特定の言語と地域向けに実際に翻訳とロケール固有のフォーマットを提供するプロセスです。Django では、USE_I18N = True が翻訳フレームワークを有効にし、USE_L10N = True が日付、数値、カレンダーのローカライズされたフォーマットを有効にします。

Django で複数形をどのように扱いますか?

Django は Python コードの ngettext() 関数とテンプレートタグ {% blocktrans count %} を通じて複数形を処理します。gettext は複雑な複数形ルールをサポートしています — 2 つの形式(単数/複数)を持つ英語とは異なり、アラビア語などの言語には 6 つの複数形があり、ポーランド語には 3 つあります。Django の gettext 統合は、.po ファイルヘッダーの複数形定義を通じてこれらすべてを処理します。

Python の例:

from django.utils.translation import ngettext

def item_count_message(count):
    return ngettext(
        "You have %(count)d item.",
        "You have %(count)d items.",
        count,
    ) % {"count": count}

テンプレートの例:

{% load i18n %}
{% blocktrans count counter=notifications %}
    You have {{ counter }} new notification.
{% plural %}
    You have {{ counter }} new notifications.
{% endblocktrans %}

Django の .po ファイルを翻訳するために AI を使用できますか?

はい。AI 翻訳モデルは .po ファイルを解析し、msgstr エントリを一括翻訳できます。推奨アプローチは、polib のようなライブラリを使用して .po ファイルをプログラムで読み取り、未翻訳の文字列を翻訳 API に送信し、結果を fuzzy フラグ付きで書き戻し、その後ネイティブスピーカーが出力をレビューすることです。Better i18n のようなツールはレビューワークフローを含むこのパイプライン全体を自動化するため、カスタムスクリプトを維持する必要がありません。重要な考慮事項は、AI 翻訳を本番環境にデプロイする前に常に人間によるレビュー用にマークすることです。

Comments

Loading comments...