# General Translation Knowledge Base: Flask や FastAPI のアプリを翻訳するには？
URL: https://generaltranslation.com/ja/kb/translate-python-app.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: ユーザー向けの文字列をマークし、リクエストごとのロケールを（通常は Accept-Language から）決定して、そのロケールの翻訳を読み込みます。Flask アプリでは通常 Flask-Babel と gettext カタログを使い、FastAPI アプリでは Babel をミドルウェアに組み込みます。General Translation の CLI を使えばこれらのカタログを埋めることができ、実験的な SDK である gt-flask と gt-fastapi を使えば、カタログを使わずにインラインの t() 文字列を翻訳できます。
Category: 国際化
Last updated: 2026-10-28
Authors: General Translation Team
Published: 2026-10-28

Flask も FastAPI も翻訳機能を標準では備えていないため、次の 3 つを自分で追加する必要があります。ユーザー向けの文字列をマークする仕組み、リクエストごとに言語を決定する仕組み、そして対応するすべての言語の翻訳です。定番の方法は Babel の gettext カタログを使うものですが、より新しい方法として、文字列をインラインのまま保持し、翻訳を JSON として読み込むやり方もあります。どちらの方法でも、1 台のサーバーが複数の言語のユーザーに同時に応答するため、言語はプロセス単位ではなくリクエスト単位で扱います。

## 翻訳対応の Python Web アプリに必要なもの

* **マークされた文字列。** コードでは文字列をそのまま返すのではなく、`gettext()` や [`t()`](/docs/python/reference/functions/t) などの関数を呼び出します。テンプレートでは `_()` または `{% trans %}` を使用します。マークしておくことで、ツールがすべての文字列を検出し、対応する翻訳を検索できるようになります。
* **リクエストごとのロケール。** 通常は `Accept-Language` ヘッダーから取得し、URL プレフィックス、クッキー、またはアカウント設定があればそちらを優先します。Flask ではリクエストコンテキストに保持します。一方、FastAPI は 1 つのイベントループ上でリクエストを並行処理するため、ロケールは `contextvars.ContextVar` に格納し、リクエストごとに設定します。
* **各ロケールの翻訳。** gettext はコンパイル済みの `.mo` カタログを読み込み、General Translation の SDK はロケールごとに JSON ファイルを読み込みます。どちらの場合も、翻訳が見つからなければエラーにはならず原文にフォールバックします。そのため、未翻訳の文字列はクラッシュを引き起こすことなく、英語のまま表示されます。

## 選択肢 1: Babel を使った gettext カタログ

[Babel](https://babel.pocoo.org/en/latest/cmdline.html) は、マークされた文字列を POT テンプレートに抽出し、言語ごとに PO カタログを作成・更新したうえで、`.mo` ファイルにコンパイルします。また、任意のロケールに応じて日付、数値、通貨の書式設定も行えます。

### Flask と Flask-Babel

[Flask-Babel](https://python-babel.github.io/flask-babel/) (4.0.0) は、Babel を Flask のリクエストや Jinja テンプレートと連携させるライブラリです。各リクエストのロケールを返す `locale_selector` を渡して使います：

```python title="app.py"
from flask import Flask, render_template, request
from flask_babel import Babel, gettext, ngettext


def select_locale():
    # Accept-Language ヘッダーと、提供している言語の中から最も一致するものを選ぶ
    return request.accept_languages.best_match(["en", "es", "pl"])


app = Flask(__name__)
babel = Babel(app, locale_selector=select_locale)


@app.route("/inbox/<int:count>")
def inbox(count):
    return {
        "title": gettext("Inbox"),
        # ngettext はアクティブなロケールに応じた複数形を選択する
        "unread": ngettext("You have %(num)d unread message.", "You have %(num)d unread messages.", count),
    }


@app.route("/")
def home():
    return render_template("home.html", name="Alice")
```

テンプレートでは、`{{ _("Welcome back, %(name)s!", name=name) }}` のように `_()` で文字列をマークします。`babel.cfg` ファイルで `pybabel` の走査対象を指定し、次の 4 つのコマンドでカタログを管理します。

```bash
# babel.cfg の内容: [python: **.py] と [jinja2: **/templates/**.html]
pybabel extract -F babel.cfg -o messages.pot .
pybabel init -i messages.pot -d translations -l es    # 言語ごとに 1 回だけ実行
pybabel update -i messages.pot -d translations        # extract のたびに全言語に対して実行
pybabel compile -d translations
```

General Translation の CLI が書き出したスペイン語のカタログを使うと、`Accept-Language: es-MX,es;q=0.9` を指定したリクエストには「Bandeja de entrada」と「Tienes 5 mensajes sin leer.」が返されました。一方、ポーランド語のカタログを作成しただけで中身が空のままだと、ポーランド語のリクエストではすべての文字列が英語で返され、警告は一切出ませんでした。

### FastAPI で Babel を使う

FastAPI には、Flask-Babel に相当する公式パッケージがありません。[fastapi-babel](https://pypi.org/project/fastapi-babel/) や [starlette-babel](https://pypi.org/project/starlette-babel/) といったサードパーティ製パッケージを利用するか、ミドルウェア内で Babel のカタログを自前で読み込む方法があります。以下の例では、同じ `translations` ディレクトリを読み込み、リクエストごとのカタログを `ContextVar` に保持します。

```python title="main.py"
from contextvars import ContextVar
from functools import lru_cache

from babel.core import negotiate_locale
from babel.support import Translations
from fastapi import FastAPI, Request

SUPPORTED = ["en", "es", "pl"]
current = ContextVar("translations")


@lru_cache
def load(locale: str) -> Translations:
    # pybabel でコンパイルされた translations/<locale>/LC_MESSAGES/messages.mo を読み込む
    return Translations.load("translations", [locale])


def _(message: str) -> str:
    return current.get().gettext(message)


def preferred_locales(header: str) -> list[str]:
    """Accept-Language tags, most preferred first, without q=0 entries."""
    weighted = []
    for part in header.split(","):
        tag, sep, param = part.strip().partition(";")
        try:
            q = float(param.strip().removeprefix("q=")) if sep else 1.0
        except ValueError:
            q = 0.0
        # q=0 は「受け入れ不可」を意味するため、スキップする
        if tag and q > 0:
            weighted.append((q, tag.replace("-", "_")))
    # sorted() は安定ソートなので、重みが等しいタグはヘッダー内の順序を保持する
    return [tag for q, tag in sorted(weighted, key=lambda pair: -pair[0])]


app = FastAPI()


@app.middleware("http")
async def select_locale(request: Request, call_next):
    preferred = preferred_locales(request.headers.get("accept-language", ""))
    current.set(load(negotiate_locale(preferred, SUPPORTED) or "en"))
    return await call_next(request)


@app.get("/inbox/{count}")
def inbox(count: int):
    t = current.get()
    return {
        "title": _("Inbox"),
        "unread": t.ngettext("You have %(num)d unread message.", "You have %(num)d unread messages.", count) % {"num": count},
    }
```

Uvicorn で実行したところ、`Accept-Language: es-MX,es;q=0.9,en;q=0.8` ではスペイン語の文字列が返され、`de,en;q=0.5` では英語にフォールバックし、`en;q=0.5,es` ではスペイン語、`es;q=0,en` では英語が返されました。`negotiate_locale` は `es_MX` を `es` にマッチさせます。`preferred_locales` はタグを `q` の重みの順に並べ替え、`q=0` のものを除外します。`q=0` は、ユーザーが受け入れない言語を表します。

### Babel を使う場合に自前で対応が必要なこと

Babel はカタログを管理するだけで、翻訳そのものは行いません。新規または変更された文字列は、誰かが各言語の `msgstr` を埋めるまで反映されません。ポーランド語なら 3 つの複数形スロットをすべて埋める必要があり、そのうえで `pybabel compile` を実行してデプロイするまで待つことになります。 (ファイル形式と、壊れたエントリを検出するためのチェック方法については [How do I translate PO and POT files?](/kb/translate-po-files) を、各ツールの対応範囲については [General Translation vs. Babel and Flask-Babel](/kb/general-translation-vs-flask-babel) を参照してください。) Django アプリでも、コマンドは異なるものの同じパターンになります。 ([General Translation vs. Django&#39;s translation framework](/kb/general-translation-vs-django-i18n) を参照してください。)

## リクエストごとにロケールを選択する

`Accept-Language` には、ユーザーの言語が任意の `q` 重み付きで列挙されています。これを自分のアプリが提供している言語とマッチングし、該当しない場合はデフォルトを適用するのが実装側の役割です。Werkzeug の `request.accept_languages` に対する `best_match()` も、Babel の `negotiate_locale` も、`es-MX` のような地域付きのタグから `es` へフォールバックします。ただし、ユーザーが明示的に選択した言語はヘッダーより優先させましょう。たとえば、検索エンジンにインデックスさせるページでは `/es/` のような URL プレフィックスを、それ以外では言語切り替え機能が設定するクッキーを使います (シグナル全体の優先順位については [ユーザーのブラウザ言語を検出するには？](/kb/detect-user-language) を参照してください) 。Node.js サーバーでは、同じ問題を `AsyncLocalStorage` で解決します ([Node.js アプリを翻訳するには？](/kb/translate-node-app) を参照してください) 。

## アプローチの比較

| アプローチ                                             | 文字列の書き方                                                         | リクエストごとのロケール                  | 翻訳の担当者                          | ステータス                            |
| ------------------------------------------------- | --------------------------------------------------------------- | ----------------------------- | ------------------------------- | -------------------------------- |
| Flask-Babel と PO カタログ                             | `gettext`、`ngettext`、Jinja の `_()`                              | `locale_selector`             | 開発者または翻訳者 (カタログごと)              | 安定版                              |
| FastAPI ミドルウェア内の Babel                            | `ContextVar` 経由の `gettext`                                      | 独自のミドルウェア                     | 開発者または翻訳者 (カタログごと)              | 安定版 (自前で構築)                      |
| リクエストごとの機械翻訳 API                                  | 実行時に生の文字列を送信                                                    | 対象言語を自分で渡す                    | API (リクエストのたびに翻訳)               | キャッシュしない限り、呼び出しごとにレイテンシとコストが発生   |
| Babel カタログ + General Translation の CLI            | Babel と同じ                                                       | Babel と同じ                     | General Translation (デプロイ前に翻訳)  | CLI は安定版、Babel は従来どおり            |
| General Translation の `gt-flask` または `gt-fastapi` | ICU メッセージを使ったインラインの [`t()`](/docs/python/reference/functions/t) | 組み込みの `Accept-Language` マッチング | General Translation (デプロイ前に翻訳)  | 実験的 (0.1.0) 。読み込まれた翻訳は 60 秒で期限切れ |

すでにカタログや翻訳者がそろっている場合は、Flask-Babel、または FastAPI ミドルウェア内の Babel を使用してください。翻訳作業を挟まずに、変更のたびにすべての言語のカタログを埋めたい場合は、そのセットアップに General Translation の CLI を追加します。カタログファイルを用意せずに文字列をインラインで記述したいプロトタイプでは、`gt-flask` または `gt-fastapi` を試してみてください。その際はバージョンを固定しておきましょう。リクエスト時に機械翻訳 API を呼び出すのは、ユーザーの投稿など、実行時にしか存在しないコンテンツに限定しましょう。

## General TranslationでFlaskおよびFastAPIアプリを翻訳する仕組み

### Babel はそのまま使い、カタログを翻訳する

General Translation の CLI である `gt` は、英語の PO カタログを読み込み、他言語のカタログを Babel が想定する場所に書き出します。まず `pybabel init -l en` で英語のカタログを一度だけ作成し、抽出のたびに `pybabel update -l en` で更新してください。そのうえで、`gt.config.json` でこのカタログを指定します。

```json title="gt.config.json"
{
  "defaultLocale": "en",
  "locales": ["es", "pl"],
  "files": {
    "pot": {
      "include": ["translations/[locale]/LC_MESSAGES/messages.po"],
      "transformationFormat": "PO"
    }
  }
}
```

`gt` 2.26.11 で確認したところ、CLI は `translations/en/LC_MESSAGES` にある英語のカタログを、同じ階層にある `es` および `pl` フォルダーに対応付けます。[`npx gt translate`](/docs/cli/reference/commands/translate) を実行し、続けて `pybabel compile -d translations` を実行してください。General Translation は対象言語ごとにすべての複数形スロットを埋め、各複数形に `%(num)d` などのプレースホルダーが含まれているかをチェックします。[コンテキストグループ](/docs/platform/dashboard/guides/defining-context-for-translations)の用語集とカスタムプロンプトを使えば、他のプロジェクトと用語の一貫性を保てます。CLI は Node.js 上で動作するため、CLI を実行するマシンには Node が必要です。一方、アプリはこれまでどおりコンパイル済みのカタログを読み込むだけで、General Translation へのランタイム依存はありません。

### または gt-flask と gt-fastapi を使う

General Translation の Python SDK である `gt-flask` と `gt-fastapi` (PyPI 上のバージョンは 0.1.0、[gt-python](https://github.com/generaltranslation/gt-python) リポジトリで公開) は実験的な段階にあり、リリースごとに変更される可能性があります。Python 3.10 以降が必要です。文字列は [`t`](/docs/python/reference/functions/t) に渡す ICU メッセージとしてコード内に記述し、値はキーワード引数として渡します。また、[`initialize_gt`](/docs/python/reference/functions/initialize-gt) によって `Accept-Language` のマッチングが設定されます。以下の Flask アプリでは、ローカルの JSON ファイルから翻訳を読み込みます。

```python title="app.py"
import json
from pathlib import Path

from flask import Flask
from gt_flask import initialize_gt, t, get_locale


def load_translations(locale: str) -> dict[str, str]:
    # gt CLI が files.gt.output に書き出したファイル
    path = Path("translations") / f"{locale}.json"
    return json.loads(path.read_text()) if path.exists() else {}


app = Flask(__name__)
# Flask では、起動時に翻訳を読み込むためにここで locales を渡す
initialize_gt(app, default_locale="en", locales=["es", "fr"], load_translations=load_translations)


@app.route("/")
def index():
    return {
        "message": t("Hello, {name}!", name="Alice"),
        "unread": t("{count, plural, one {You have # unread message.} other {You have # unread messages.}}", count=3),
        "locale": get_locale(),
    }
```

FastAPI の場合は、`gt_fastapi` からインポートし、`FastAPI()` を作成して `@app.get` を使用します。FastAPI では [`initialize_gt`](/docs/python/reference/functions/initialize-gt) が HTTP ミドルウェアを追加し、アプリの lifespan 内で翻訳を読み込むため、`gt.config.json` に locales を指定するだけで十分です。General Translation の CLI が書き出した `translations/es.json` を使ったところ、どちらのアプリも次のように応答しました。

```text title="Output"
Accept-Language: es-MX,es;q=0.9   {"locale": "es", "message": "¡Hola, Alice!", "unread": "Tienes 3 mensajes sin leer."}
Accept-Language: de                {"locale": "en", "message": "Hello, Alice!", "unread": "You have 3 unread messages."}
```

これらのファイルは CLI が書き出します。CLI に検出させるため、`requirements.txt` または `pyproject.toml` に `gt-flask` または `gt-fastapi` を記載し、`gt.config.json` で出力パスを設定してください：

```json title="gt.config.json"
{
  "defaultLocale": "en",
  "locales": ["es", "fr"],
  "files": { "gt": { "output": "translations/[locale].json" } }
}
```

[`npx gt translate`](/docs/cli/reference/commands/translate) は `.py` ファイル内の [`t()`](/docs/python/reference/functions/t) 呼び出しをスキャンし(仮想環境、テスト、マイグレーションは除外)、翻訳したうえでロケールごとに1つのファイルを書き出します。このアプリでは、[`npx gt validate`](/docs/cli/reference/commands/validate) が API を呼び出すことなく、`gt-flask` 向けの翻訳可能なエントリを2件検出しました。ローカルファイルではなく General Translation の CDN から読み込む場合は、設定で `projectId` と `cacheUrl` を指定してください([翻訳の配信方法を選ぶ](/docs/python/guides/configuring#delivery)を参照)。SDK に APIキーは不要で、ローカルファイルを使えば実行時のネットワーク呼び出しも発生しません。

0.1.0 の [`t()`](/docs/python/reference/functions/t) は、60秒で期限切れになるインメモリキャッシュから翻訳を読み取りますが、この読み取り処理ではキャッシュが再読み込みされません。そのため、長時間稼働するサーバーでは、キャッシュの期限が切れてからローダーが再び実行されるまで、翻訳元文字列が返されることになります([preload に関するメモ](/docs/python/guides/configuring#preload)を参照)。今後のリリースでこの挙動が改善されるまでは、本番環境のトラフィックには CLI で生成した Babel カタログを使用し、`gt-flask` と `gt-fastapi` の利用はプロトタイプにとどめてください。

## よくある質問

### gt-flask は Flask-Babel の日付や通貨のフォーマットを置き換えますか？

いいえ。0.1.0 時点で `gt-flask` と `gt-fastapi` がエクスポートするのは、翻訳およびロケール関連のヘルパー ([`t`](/docs/python/reference/functions/t)、[`get_locale`](/docs/python/reference/functions/get-locale)、[`declare_var`](/docs/python/reference/functions/declare-var) など) のみです。ロケールに応じたフォーマットには、引き続き Babel の `format_date` と `format_currency` を使用してください。

### Accept-Language を使わずに、ユーザーが言語を選択できるようにするにはどうすればよいですか？

Flask-Babel では、`locale_selector` 内でクッキーまたは URL セグメントを読み取ります。General Translation の SDK では、[`get_locale`](/docs/python/reference/functions/get-locale) コールバックを [`initialize_gt`](/docs/python/reference/functions/initialize-gt) に渡します。このコールバックはヘッダーによるマッチングの代わりに使われるため、サポート対象のロケールを自分で返す必要があります。 ([リクエストのロケールの検出](/docs/python/guides/detecting-locale)を参照してください。)

### データベースのコンテンツやユーザーの投稿を翻訳するにはどうすればよいですか？

カタログと [`t()`](/docs/python/reference/functions/t) が扱うのは、コード内の文字列です。実行時にしか存在しないコンテンツは、作成時またはリクエスト時に翻訳 API を呼び出す必要があります。たとえば General Translation の [Translate content](/docs/platform/openapi/reference/translation/translate-content) エンドポイント (`POST /v2/projects/{projectId}/translate`) を呼び出し、翻訳結果を元のコンテンツと併せて保存します。

## 重要なポイント

* Flask と FastAPI で必要な追加は 3 つです。マークされた文字列、リクエストごとのロケール、ロケールごとの翻訳です。FastAPI ではロケールを `ContextVar` に保持します。
* Flask では、Flask-Babel が `locale_selector` と `pybabel extract`、[`init`](/docs/cli/reference/commands/init)、`update`、`compile` で対応します。FastAPI ではミドルウェアで Babel の `Translations` を読み込みます。
* カタログが空または存在しない場合は警告なしで英語にフォールバックします。そのため、残る作業はすべての `msgstr` を埋めることです。
* General Translation の CLI は、`"pot"` と `"transformationFormat": "PO"` を指定すると、複数形スロットも含めて Babel の PO カタログを埋めます。アプリ側はこれまでどおりコンパイル済みカタログを読み込めます。
* General Translation の実験的な `gt-flask` と `gt-fastapi` 0.1.0 は、CLI が書き出す JSON ファイルを使ってインラインの [`t()`](/docs/python/reference/functions/t) 文字列を翻訳します。Flask では [`initialize_gt`](/docs/python/reference/functions/initialize-gt) に `locales` を渡してください。また、本番環境のトラフィックを処理する前に、60 秒のキャッシュ有効期限への対策を検討しておきましょう。

## Related questions

- [General Translation と Babel・Flask-Babel の比較：どちらを使うべきか](https://generaltranslation.com/ja/kb/general-translation-vs-flask-babel.mdx): Babel はあらゆるロケールの日付、数値、通貨を書式設定し、gettext カタログを管理します。Flask-Babel はそれを Flask と連携させるライブラリですが、どちらも文字列の翻訳までは行いません。General Translation の CLI は、Babel の PO ファイルを複数形も含めて全言語分自動で埋めるため、Flask-Babel はそのままに、カタログを手作業で翻訳する手間から解放されます。
- [POファイルとPOTファイルはどのように翻訳すればよいですか？](https://generaltranslation.com/ja/kb/translate-po-files.mdx): Plural-Formsヘッダーが求める各複数形を含め、すべてのmsgstrを埋めます。msgid、msgctxt、プレースホルダーは変更しないでください。msgfmt --checkを実行すると、ヘッダー、複数形の数、および書式フラグ付きエントリのプレースホルダーを検査できます。空のmsgstrがないかは別途確認してください。General TranslationのCLIは、POTテンプレートから言語ごとに翻訳済みのPOファイルを書き出します。
- [ユーザーのブラウザ言語を検出するには？](https://generaltranslation.com/ja/kb/detect-user-language.mdx): ブラウザが持つ優先順位付きの言語リスト（JavaScript では navigator.languages、サーバー側では q 値で並べ替えた Accept-Language ヘッダー）を読み取り、フォールバックを用意したうえで対応ロケールとマッチングします。なお、Safari や Chrome のシークレットモードでは 1 つの言語しか通知されません。また、IP ジオロケーションでわかるのは所在地であって、言語ではありません。
- [Node.js アプリを翻訳するには？](https://generaltranslation.com/ja/kb/translate-node-app.mdx): 各リクエストの言語を（通常は Accept-Language ヘッダーから）検出し、すべての文字列をその言語で返します。General Translation の gt-node では、withGT でリクエストごとにロケールを関連付け、getGT で文字列を翻訳します。翻訳は、デプロイ前に npx gt translate --publish で生成しておきます。
- [General Translation と Django の翻訳フレームワーク：両方必要なのか？](https://generaltranslation.com/ja/kb/general-translation-vs-django-i18n.mdx): Django の翻訳フレームワークと General Translation は併用できます。メッセージの抽出、言語の選択、レンダリングは Django が担当し、General Translation は製品の用語とダッシュボードでのレビュー内容を反映して、複数形を含む PO ファイルに翻訳を書き込みます。テンプレートや URL パターンはそのままで、翻訳はコードの変更に合わせて更新されます。

## Previous and next questions

- Previous question: [en-US や en-GB などのロケールコードとは？](https://generaltranslation.com/ja/kb/locale-codes.mdx)
- Next question: [General Translation と Lingo.dev の比較：どちらを使うべきか](https://generaltranslation.com/ja/kb/general-translation-vs-lingo-dev.mdx)

## Sitemap

See the [Knowledge Base sitemap](https://generaltranslation.com/ja/kb/sitemap.md) for every article.
