# General Translation Knowledge Base: POファイルとPOTファイルはどのように翻訳すればよいですか？
URL: https://generaltranslation.com/ja/kb/translate-po-files.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: Plural-Formsヘッダーが求める各複数形を含め、すべてのmsgstrを埋めます。msgid、msgctxt、プレースホルダーは変更しないでください。msgfmt --checkを実行すると、ヘッダー、複数形の数、および書式フラグ付きエントリのプレースホルダーを検査できます。空のmsgstrがないかは別途確認してください。General TranslationのCLIは、POTテンプレートから言語ごとに翻訳済みのPOファイルを書き出します。
Category: 翻訳
Last updated: 2026-10-28
Authors: General Translation Team
Published: 2026-10-28

POT (Portable Object Template) ファイルにはコードから抽出された翻訳元文字列が格納され、各PO (Portable Object) ファイルではそれらの文字列が特定の1言語の翻訳と対応付けられます。翻訳作業では、その言語が使う複数形ごとに1つずつ、すべての`msgstr`を埋めます。`msgid`、`msgctxt`、プレースホルダーは抽出されたまま一切変更しません。その後、各POファイルを、プログラムが読み込むバイナリ形式の`.mo`カタログにコンパイルします。形式自体は単純ですが、問題があっても気づきにくいのが難点です。gettextは使用できないエントリについて、エラーを出さずに原文をそのまま表示するからです。

## POT ファイルと PO ファイルはどこから生まれるのか

gettext ベースのスタックはどれも、コマンド名こそ異なるものの、同じ 4 つのステップを踏みます。

| ステップ                        | GNU gettext | Babel (Flask、FastAPI) |
| --------------------------- | ----------- | --------------------- |
| 翻訳元文字列をテンプレートに抽出する          | `xgettext`  | `pybabel extract`     |
| 新しい言語用の PO ファイルを作成する        | `msginit`   | `pybabel init`        |
| テンプレートの変更を既存の PO ファイルにマージする | `msgmerge`  | `pybabel update`      |
| PO ファイルを `.mo` にコンパイルする     | `msgfmt`    | `pybabel compile`     |

Django では最初の 3 つのステップが `makemessages` に統合されており、PO ファイルが直接書き出されます。コンパイルには `compilemessages` を使用します。翻訳作業はマージとコンパイルの間に行います。テンプレート自体は特定の言語を持たないため、翻訳が格納されるのは PO ファイルだけです。(翻訳元文字列が変更されたときに `msgmerge` がどう動作するかなど、gettext のワークフロー全体については [General Translation と GNU gettext の比較](/kb/general-translation-vs-gettext) を参照してください。)

## PO エントリの中身

以下は、短い Python スクリプトから `xgettext` で抽出したテンプレートをもとに、General Translation の CLI が生成したポーランド語の PO ファイルです (ヘッダーは一部省略しています) 。

```text title="po/pl.po"
msgid ""
msgstr ""
"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;\n"

#: inbox.py:8
#. TRANSLATORS: Heading of the inbox page.
msgid "Inbox"
msgstr "Skrzynka odbiorcza"

#: inbox.py:9
#, python-format
msgid "You have %(num)d unread message."
msgid_plural "You have %(num)d unread messages."
msgstr[0] "Masz %(num)d nieprzeczytaną wiadomość."
msgstr[1] "Masz %(num)d nieprzeczytane wiadomości."
msgstr[2] "Masz %(num)d nieprzeczytanych wiadomości."

#: inbox.py:10
msgctxt "email action"
msgid "Archive"
msgstr "Archiwizuj"
```

[GNU gettext マニュアル](https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html)では、各部分を次のように定義しています。

* **`msgid` と `msgid_plural`** は原文で、`msgid` は参照キーも兼ねています。PO ファイル内でこれらを編集してはいけません。`msgid` を変更するとコード内のどれとも一致しなくなり、ユーザーには原文が表示されてしまいます。
* **`msgstr`** は翻訳で、自分で書くのはこの行だけです。複数形のエントリでは、`msgstr[0]` から「その言語の複数形の数 − 1」番までの `msgstr` を持ちます。
* **`msgctxt`** は、同じ文字列でも意味が異なる翻訳元文字列を区別するためのものです。たとえば、メール操作としての「Archive」とアーカイブフォルダーの「Archive」などです。`pgettext` に由来し、文脈ごとに個別の翻訳が割り当てられます。抽出された値は変更せずにそのまま残します。
* **`#.` 抽出コメント**は開発者からのメモです (`xgettext --add-comments` がコードから抽出します) 。**`#:` 参照**はソースコードの該当行を示し、**`# ` 翻訳者コメント**は翻訳を担当する人が自由に使うためのものです。
* **`#,` フラグ**には 2 種類あります。`fuzzy` はワークフロー用のフラグで、翻訳が誤っている可能性があり、人による確認が必要であることを示します。`c-format` や `python-format` などの書式フラグは `xgettext` が設定するもので、翻訳元と翻訳のプレースホルダーを照合するよう `msgfmt` に指示します。
* **ヘッダー**は、`msgid` が空のエントリです。ヘッダー内の `Language`、`Content-Type` の charset、`Plural-Forms` の各フィールドはファイル全体に適用されます。

## Plural-Forms が翻訳の数を決める仕組み

`Plural-Forms` は、形式の数 (`nplurals`) と、件数 `n` を形式のインデックスに対応付ける C 風の式を指定します ([GNU gettext マニュアル: 複数形](https://www.gnu.org/software/gettext/manual/html_node/Plural-forms.html)) 。GNU gettext 1.0 の `msginit` は、日本語には `nplurals=1; plural=0;`、ドイツ語には `nplurals=2; plural=(n != 1);`、フランス語 (ゼロを単数として扱う) には `nplurals=2; plural=(n > 1);` を書き出し、ポーランド語とロシア語には 3 つの形式を書き出しました。アラビア語では 6 つの形式が必要です。ところが、同じ `msginit` でもアラビア語の場合はヘッダーにプレースホルダー `nplurals=INTEGER; plural=EXPRESSION;` が残ったままになるため、翻訳を始める前に必ずヘッダーを確認してください。

ポーランド語のファイルをコンパイルすると、Python の `ngettext` は件数に応じて異なるスロットを選択します：

```text title="Output"
count  slot        output
1      msgstr[0]   Masz 1 nieprzeczytaną wiadomość.
3      msgstr[1]   Masz 3 nieprzeczytane wiadomości.
5      msgstr[2]   Masz 5 nieprzeczytanych wiadomości.
22     msgstr[1]   Masz 22 nieprzeczytane wiadomości.
```

英語の翻訳元には複数形が2つしかないため、エントリ単位で2つのポーランド語の文字列に翻訳すると、`msgstr[2]` には 5 や 12 に対して表示できる正しい訳が存在しなくなります。

## PO ファイルの翻訳で起こる問題

以下はいずれもコンパイルや実行時にクラッシュしないため、気づかれないまま本番環境に入り込んでしまいます。

* **fuzzy エントリはユーザーに届きません。** `msgfmt` は `--use-fuzzy` を渡さない限り、fuzzy エントリを `.mo` ファイルから除外します。なお、マニュアルではこのオプションの使用を「通常は誤り」としています。複数形のエントリを fuzzy にしたところ、ポーランド語のビルドでは &quot;You have 5 unread messages.&quot; が英語のまま表示されました。fuzzy エントリは一つずつレビューしてから、フラグを削除してください。
* **複数形のスロットが空だと何も出力されないことがあります。** `msgfmt` は `msgstr[2]` が空のエントリをそのままコンパイルして翻訳済みとして数え、Python は件数 5 に対して空行を出力しました。`--check` がこれを拒否するのは、エントリに書式フラグがある場合に限られます。この場合、空のスロットにプレースホルダーが含まれないことで検出されるためです。フラグがなければ、エントリはチェックを通過してしまいます。
* **プレースホルダーが欠落したり、名前が変わったりします。** `%(num)d` が含まれない翻訳では、件数が表示されなくなります。`msgfmt --check` がこれを検出できるのは、書式フラグのあるエントリに限られます。
* **引用符とエスケープは C 言語形式です。** `msgstr` 内の `"` は `\"` と記述する必要があり、エスケープされていない引用符は構文エラーになります。また、文字列内の改行は `\n` と記述します。
* **テンプレートの charset はプレースホルダーのままです。** `xgettext` は POT ヘッダーに `charset=CHARSET` と書き込みますが、`msgfmt --check` はこの記述が残っている PO ファイルを拒否します。`msginit` と `pybabel` は UTF-8 を書き込みます。ツールでテンプレートのコピーを直接翻訳する場合は、事前にテンプレートに対して `msgconv --to-code=UTF-8` を実行してください。

`msgfmt --check` は検出できる問題を報告し、`--statistics` は実際にビルドに含まれた件数を集計します。

```text title="msgfmt --check"
$ msgfmt --check -o /dev/null missing-placeholder.po
missing-placeholder.po:30: a format specification for argument 'num' doesn't exist in 'msgstr[2]'
msgfmt: found 1 fatal error

$ msgfmt --check -o /dev/null two-forms.po
two-forms.po:7: nplurals = 3...
two-forms.po:30: ...but some messages have only 2 plural forms
msgfmt: found 1 fatal error

$ msgfmt --check --statistics -o inbox.mo fuzzy.po
2 translated messages, 1 fuzzy translation.
```

CI ですべての PO ファイルに対して実行し、fuzzy の件数が 1 件でもあれば未完了の作業として扱ってください。

複数形エントリのスロットが 1 つだけ空になっていても、それを検出できる gettext ツールはありません。`msgfmt --check` はエントリに書式フラグがなければそのまま通してしまい、`msgattrib --untranslated` はそのエントリをスキップします。[Babel](https://babel.pocoo.org/) (`pip install babel`) を使った短いスクリプトを用意すれば、空の翻訳が 1 つでもある場合にビルドを失敗させられます。

```python title="check_po.py"
import sys

from babel.messages.pofile import read_po

failed = False
for path in sys.argv[1:]:
    with open(path, "rb") as f:
        catalog = read_po(f)
    for message in catalog:
        if not message.id or message.fuzzy:
            continue  # ヘッダーと、msgfmt がもともとスキップする fuzzy エントリは除外
        forms = message.string if isinstance(message.string, tuple) else (message.string,)
        if not all(forms):
            print(f"{path}:{message.lineno}: empty translation for {message.id!r}")
            failed = True
sys.exit(1 if failed else 0)
```

```text title="Output"
$ python check_po.py po/pl.po
po/pl.po:17: empty translation for ('You have %(num)d unread message.', 'You have %(num)d unread messages.')
```

## PO ファイルを翻訳する方法

| 方法                                                                                       | 翻訳する人                       | 複数形スロット                          | 翻訳者が得られる文脈                               | テンプレートが変更されたとき                                                                                                           |
| ---------------------------------------------------------------------------------------- | --------------------------- | -------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| 手作業または [Poedit](https://poedit.com/features/) で編集                                        | 自分自身または翻訳者                  | Poedit が誤った複数形や欠落したトークンを警告       | ファイル内のコメントと参照情報                          | `msgmerge` を実行し、fuzzy エントリをレビュー                                                                                          |
| [Weblate](https://docs.weblate.org/en/latest/formats/gettext.html) またはその他の翻訳管理システム (TMS) | 翻訳者 (機械翻訳の提案も利用可能)          | 対応済み。fuzzy は「Needs editing」に対応   | コメント、参照情報、および TMS のスクリーンショットや用語集         | msgmerge アドオンまたはリポジトリ同期で PO ファイルを更新                                                                                      |
| GNU gettext の `msgpre`                                                                   | ollama 経由のローカル LLM (下訳として)  | 翻訳者がレビューするための下訳を作成               | PO ファイルに含まれる情報                           | 新しいエントリに対して再実行 ([gettext 1.0 pretranslation](https://www.gnu.org/software/gettext/manual/html_node/Pretranslating.html)) |
| 機械翻訳 API または LLM を呼び出すスクリプト                                                              | モデル                         | ヘッダーが要求するすべてのスロットをスクリプトで出力する必要あり | スクリプトが送信する内容のみ                           | 再実行し、出力を自分で検証                                                                                                            |
| [General Translation の CLI](/docs/cli/reference/formats/po-pot-files)                    | General Translation の翻訳サービス | 対象言語のすべてのスロットを出力し、プレースホルダーもチェック  | `msgctxt`、コメント、参照情報、フラグに加え、用語集とカスタムプロンプト | 変更のない翻訳は再利用し、新規または変更されたエントリのみを翻訳                                                                                         |

Poedit の機械翻訳による事前翻訳は Pro 版の機能ですが、編集、翻訳メモリ、各種チェックは無料版でも利用できます。(TMS を介した一連のやり取りとの比較については、[General Translation vs. Weblate](/kb/general-translation-vs-weblate) を参照してください。)

1〜2 言語を自分で翻訳するなら Poedit が適しています。プロの翻訳者がカタログを管理し、作業の割り当てやレビューキューが必要な場合は、Weblate などの [TMS](/kb/what-is-a-translation-management-system) を使いましょう。スクリプトや `msgpre` は、検証まで自分で責任を持てる場合に限って使用してください。ビルドのたびに PO ファイルを全言語へ翻訳し、他のプロダクトとも用語を共有したい場合は、General Translation が最適です。

## General Translation で PO ファイルを翻訳する

General Translation の CLI である `gt` は、POT (またはソース言語の PO) ファイルを読み込み、ロケールごとに翻訳済みの PO ファイルを出力します。`gt.config.json` に `pot` エントリを追加してください。この設定は `gt` 2.26.11 で動作確認済みです。CLI 自体のパス解決により、`po/messages.pot` は `po/de.po`、`po/pl.po`、`po/pt_BR.po` にマッピングされます：

```json title="gt.config.json"
{
  "defaultLocale": "en",
  "locales": ["de", "pl", "pt_BR"],
  "customMapping": { "pt_BR": { "code": "pt-BR" } },
  "files": {
    "pot": {
      "include": ["po/messages.pot"],
      "transform": { "match": "^po/messages\\.pot$", "replace": "po/{locale}.po" },
      "transformationFormat": "PO"
    }
  }
}
```

* **`pot`** はPOファイルとPOTファイルの両方を読み込みます (`POT` と指定しても動作します) 。POおよびPOTのサポートは `gt` 2.14.22で追加されました。
* **`"transformationFormat": "PO"`** は必須です。指定しない場合、出力は未翻訳のテンプレートのままになります。
* **[`transform`](/docs/cli/reference/config#locale-placeholders)** は各出力ファイルの名前を指定します。`{locale}` には `locales` に記述したとおりのロケールが入ります。また、[`customMapping`](/docs/cli/reference/config#custom-mapping) はgettextの `pt_BR` を標準コードの `pt-BR` にマッピングします。
* **BabelおよびDjangoのレイアウト**では `transform` は不要です。`include` に、フォルダ部分に `[locale]` プレースホルダーを含む英語のカタログのパス (例：`translations/` 配下の `[locale]/LC_MESSAGES/*.po`) を指定すると、CLIは各言語のカタログをその隣 (例：`pl/LC_MESSAGES/messages.po`) に書き出します。

次に、抽出とコンパイルの間でCLIを実行します：

```bash
xgettext --language=Python --keyword=pgettext:1c,2 --add-comments=TRANSLATORS -o po/messages.pot app.py
msgconv --to-code=UTF-8 -o po/messages.pot po/messages.pot
npx gt translate
msgfmt --check -o locale/pl/LC_MESSAGES/messages.mo po/pl.po
```

まず [`npx gt login`](/docs/cli/reference/commands/login) とプロジェクトIDを使って、CIの場合はプロジェクトのAPIキーを使って認証情報を設定してください ([CLIの認証情報](/docs/cli/guides/configuring#credentials)) 。POファイルにレビュー済みの翻訳がすでに含まれている場合は、最初に [`npx gt translate`](/docs/cli/reference/commands/translate) を実行する前に [`npx gt upload`](/docs/cli/reference/commands/upload) でインポートしてください。

General Translationは各エントリの `msgid` テキストを翻訳し、その際に `msgctxt`、`#.` コメント、`#:` 参照、フラグを文脈として併せて送信します。出力の `Language` ヘッダーを設定し、ファイルに複数形のエントリがある場合は、対象言語の `Plural-Forms` ルールを書き込んだうえで、そのルールが求めるすべての `msgstr[n]` を埋めます (ポーランド語なら3つ、アラビア語なら6つ) 。複数形の各翻訳では、空のスロットがないか、`%(num)d`、`%s`、`{name}` などのプレースホルダーがいずれかの形で欠落していないかがチェックされます。コメント、参照、フラグ、`msgctxt` はテンプレートから引き継がれ、変更のないエントリには次回の実行時に以前の翻訳が再利用されます。

翻訳元が `msgid` であるため、gettextのデフォルトどおり原文をキーとして使うコードで最も効果を発揮します。レビュー担当者は[ダッシュボードでエントリを編集](/docs/platform/dashboard/guides/reviewing-translations)できます。また、[コンテキストグループ](/docs/platform/dashboard/guides/defining-context-for-translations)の用語集とカスタムプロンプトを使えば、「Archive」のような用語の訳をWebアプリやドキュメントと統一できます。POファイルをローカルで編集した場合は、次回の実行前に [`npx gt save-local`](/docs/cli/reference/commands/save-local) で同期してください。フレームワーク別のセットアップについては、[General TranslationとDjangoの翻訳フレームワークの比較](/kb/general-translation-vs-django-i18n)、[General TranslationとBabelおよびFlask-Babelの比較](/kb/general-translation-vs-flask-babel)、[FlaskやFastAPIアプリを翻訳するには？](/kb/translate-python-app)を参照してください。

## よくある質問

### PO ファイルと POT ファイルの違いは何ですか？

POT ファイルはテンプレートです。`msgstr` はすべて空で、ヘッダーには言語が指定されていません。PO ファイルは特定の 1 言語向けのコピーで、`Language` ヘッダー、その言語の `Plural-Forms` ルール、そして翻訳が含まれています。

### General Translation が PO ファイルを書き出す場合でも msgmerge は必要ですか？

POT テンプレートを翻訳元とする場合、対象言語については不要です。実行のたびに現在のテンプレートからすべての言語の PO ファイルが書き出されるため、コードから削除されたエントリは消え、新しいエントリは翻訳済みの状態で追加されます。解消が必要な fuzzy エントリも発生しません。ただし、Babel や Django のレイアウトでは、英語のカタログは引き続き `pybabel update` または `makemessages` で更新します。

### PO ファイルを翻訳したのに、アプリが英語のまま表示されるのはなぜですか？

主な原因は次の 3 つです。エントリがまだ `fuzzy` としてマークされている、`.mo` ファイルを再コンパイルまたは再デプロイしていない、あるいは PO ファイル内の `msgid` がコード内の文字列と一致しなくなっている、のいずれかです。`msgfmt --statistics` を実行すると、翻訳済みのエントリと fuzzy のエントリがそれぞれいくつあるかを確認できます。

## 重要なポイント

* 翻訳するのは `msgstr` だけです。`msgid`、`msgid_plural`、`msgctxt` は参照キーなので、抽出された状態のまま変更しません。
* `msgstr[n]` のスロットは、ヘッダーの `nplurals` で指定された数だけ記述します。日本語は 1 つ、ドイツ語は 2 つ、ポーランド語は 3 つ、アラビア語は 6 つです。
* `msgfmt` は fuzzy エントリを除外する一方、空の複数形スロットはそのままコンパイルするため、実行時には何も表示されません。`--check` でこれを検出できるのは書式フラグ付きのエントリに限られるので、すべての PO ファイルに対して `msgfmt --check --statistics` を実行し、あわせて空の翻訳がないかを別途チェックしてください。
* General Translation の CLI では、`"pot"`、`"transformationFormat": "PO"`、`transform` を指定すると、POT テンプレートからロケールごとの PO ファイルを翻訳・生成し、対象言語に必要な複数形スロットをすべて埋めます。

## Related questions

- [General Translation と GNU gettext：両方必要か？](https://generaltranslation.com/ja/kb/general-translation-vs-gettext.mdx): 抽出とレンダリングには GNU gettext を引き続き使い、PO ファイルの生成と保守には General Translation を導入しましょう。General Translation は翻訳者向けメモやメッセージの文脈を生成に反映し、共通の用語を適用するほか、レビュー担当者向けに ダッシュボード エディターを提供します。既存の gettext 呼び出しやコンパイル済みのカタログには手を加える必要はありません。
- [General Translation と Django の翻訳フレームワーク：両方必要なのか？](https://generaltranslation.com/ja/kb/general-translation-vs-django-i18n.mdx): Django の翻訳フレームワークと General Translation は併用できます。メッセージの抽出、言語の選択、レンダリングは Django が担当し、General Translation は製品の用語とダッシュボードでのレビュー内容を反映して、複数形を含む PO ファイルに翻訳を書き込みます。テンプレートや URL パターンはそのままで、翻訳はコードの変更に合わせて更新されます。
- [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 はそのままに、カタログを手作業で翻訳する手間から解放されます。
- [Flask や FastAPI のアプリを翻訳するには？](https://generaltranslation.com/ja/kb/translate-python-app.mdx): ユーザー向けの文字列をマークし、リクエストごとのロケールを（通常は Accept-Language から）決定して、そのロケールの翻訳を読み込みます。Flask アプリでは通常 Flask-Babel と gettext カタログを使い、FastAPI アプリでは Babel をミドルウェアに組み込みます。General Translation の CLI を使えばこれらのカタログを埋めることができ、実験的な SDK である gt-flask と gt-fastapi を使えば、カタログを使わずにインラインの t() 文字列を翻訳できます。
- [General Translation と Weblate の比較：どちらを使うべきか](https://generaltranslation.com/ja/kb/general-translation-vs-weblate.mdx): Weblate は、コミュニティやチームでリポジトリ内のファイルを翻訳するために作られた、セルフホスト可能な Git ベースのローカライゼーションプラットフォームです。General Translation は、マークされた JSX と共有されたプロダクトの文脈を用いて、翻訳の生成を開発とリリースに組み込みます。既存の gettext ファイルをそのまま使い続けることも、インライン記述を導入して翻訳元カタログの保守そのものをなくすこともできます。

## Previous and next questions

- Previous question: [Payload CMS でコンテンツを翻訳するにはどうすればよいですか？](https://generaltranslation.com/ja/kb/translate-payload-content.mdx)
- Next question: [SEO用のタイトルとメタディスクリプションはどのように翻訳すればよいですか？](https://generaltranslation.com/ja/kb/translate-seo-metadata.mdx)

## Sitemap

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