Djangoの多言語対応:"Polish"が"ポーランド語"になる悲劇を防ぐ(Contextual Markers)
多言語(i18n)を正しくサポートすると、こんな経験をすることがあります。
- ボタンに “Polish”(UIを整える)と書いておくと、翻訳結果が “ポーランド語” になる
- 日付選択コンポーネントの “May” が、ある言語では 人名 のように翻訳される
- メニューの “Book” が「本」だけに翻訳され、"予約する" には翻訳されない
問題の原因は簡単です。
コンピュータは「文脈(context)」を理解しません。 文字列が同じならすべて同じものとして扱います。
この記事では、Djangoの Contextual Markers を活用して、ソースコードはそのままに、 同一文字列に対して異なる翻訳を付与する方法を整理します。
なぜこうなるのか? gettextの基本動作
Djangoの翻訳システムは内部で GNU gettext を使用します。 gettextは非常に単純なルールで動作します。
- 原文文字列 →
msgid - 翻訳文字列 →
msgstr msgidが同じなら必ず同じmsgstrを使用
# django.po
msgid "Polish"
msgstr "ポーランド語"
このように .po ファイルに一度マッピングが作られると、
どこで "Polish" を使っても 常に同じ翻訳 が使われます。
したがって、次の二つのUIは区別できません。
- "Polish" = UIを整える動詞
- "Polish" = 言語/国名
これを避けるためにソースコードをこう変更するケースがあります。
<!-- 避けるべきコード -->
{% trans "Polish (verb)" %}
{% trans "Polish (language)" %}
こうすると翻訳はうまくいくものの、 ユーザーに表示される文字列自体が変になる か 他の場所で再利用しにくくなる問題が生じます。
私たちが望むのは:
- ソース文字列はまだ
"Polish"のまま - 「今どの意味で使われているか」を別途説明 すること
このとき使うのが Contextual Marker(文脈情報) です。
テンプレートで解決する: {% translate %} + context
Djangoテンプレートでは {% translate %}(または旧スタイル {% trans %})タグに
context オプションを付けて、同一文字列を文脈別に区別できます。
1) 既存コード(衝突発生)
{% load i18n %}
<button>{% translate "Polish" %}</button>
<span>{% translate "Polish" %}</span>
.po ファイルでは両方とも msgid "Polish" になるので
一方の意味だけに翻訳 できます。
2) 改善されたコード(context 使用)
{% load i18n %}
<button>
{% translate "Polish" context "verb: to refine UI" %}
</button>
<span>
{% translate "Polish" context "language name" %}
</span>
ここで重要なのは:
contextの後の文字列は ユーザーには表示されません。- 翻訳システムと翻訳者のための メタ情報 です。
-
意味が明確になるように短く説明するのがベスト。
-
"verb: to refine UI" "language name""menu label""button text"など
3) {% blocktranslate %} でも使用可能
文が長い場合 {% blocktranslate %}(または {% blocktrans %})を使うときも context を付けられます。
{% load i18n %}
{% blocktranslate context "greeting message" with username=user.username %}
Hello {{ username }}
{% endblocktranslate %}
こうすれば同じ "Hello %(username)s" スタイルの文でも
別の文脈で複数回使えます。
Pythonコードで解決する: pgettext
テンプレートではなく views, models, forms などの Python コード内では
gettext の代わりに pgettext 系関数 を使用します。
代表的に次の3つがあります。
pgettext(context, message)pgettext_lazy(context, message)– 遅延評価用(モデルフィールド、モジュールレベルなど)npgettext(context, singular, plural, number)– 複数形 + 文脈同時処理
1) 基本例
from django.utils.translation import pgettext
def my_view(request):
# 1. 月(month)名 "May"
month = pgettext("month name", "May")
# 2. 人名 "May"
person = pgettext("person name", "May")
# 3. 助動詞 "may" (~かもしれない)
verb = pgettext("auxiliary verb", "may")
こうすれば三つのケースすべてで msgid は "May"/"may" ですが、
異なる context によってそれぞれ別の翻訳が可能です。
2) モデルで pgettext_lazy を使う
モデルフィールドの verbose_name や help_text、
あるいは choices に入るラベルでも同音異義語の問題が起きます。
from django.db import models
from django.utils.translation import pgettext_lazy
class Order(models.Model):
# "Order" = 注文
type = models.CharField(
verbose_name=pgettext_lazy("order model field", "Order type"),
max_length=20,
)
STATUS_CHOICES = [
# "Open" = 状態
("open", pgettext_lazy("order status", "Open")),
# "Open" = 動作(開く)
("opened", pgettext_lazy("log action", "Open")),
]
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
)
上記のように:
- 同じ
"Open"でも - 「状態(status)」 と 「行動(action)」 を明確に分けられます。
3) 複数形まで一緒に処理: npgettext
同一語が複数形で意味が変わる場合は
npgettext を使って文脈 + 複数形を同時に処理できます。
from django.utils.translation import npgettext
def get_notification(count):
# "Message" を通知用に使うと仮定
return npgettext(
"user notification", # context
"You have %(count)d message", # singular
"You have %(count)d messages", # plural
count
) % {"count": count}
.po ファイルではどう見えるか?
上記のように context を使った後:
python manage.py makemessages -l ko
同じコマンドでメッセージを抽出すると、
.po ファイルには msgctxt フィールドが追加されます。
# django.po
# 1) "Polish" = UIを整える動詞
msgctxt "verb: to refine UI"
msgid "Polish"
msgstr "整える"
# 2) "Polish" = 言語/国名
msgctxt "language name"
msgid "Polish"
msgstr "ポーランド語"
msgid は同じですが:
msgctxtが異なるため- 翻訳ツール(Poedit, Weblate, Crowdin など)でも 別々の項目 として扱われます。
- 翻訳者は実際の UI でどのように使われるかを簡単に理解できます。
重要なのは:
- ソースコードの文字列はまったく変わらない (
"Polish"そのまま) .poファイルの構造だけが賢くなる
良い Context の書き方のヒント
文脈文字列はユーザーには表示されませんが、 翻訳者にとってはほぼ唯一のヒント です。以下の基準を推奨します。
1) 「役割」を説明するように書く
"button label"– ボタンに使われるテキスト"menu item"– ナビゲーションメニュー"tooltip"– マウスオーバーテキスト"error message"– エラーメッセージ"form field label"– フォームラベル
文字列の UI 役割 を説明すると、 ほぼすべての言語で自動翻訳を経ても意味が保たれます。
2) 「どの概念か」をもう一度書く
同一語が複数概念で使われる場合:
"File""file menu item"-
"uploaded file object" -
"Order" "e-commerce order""sorting order"
このように ドメイン概念 を一緒に書くと、 多言語翻訳でも意味がより安定します。
3) 翻訳文字列を context に入れない
context は説明領域です。
ここに翻訳結果(別言語)を直接書くと:
- 言語が増えるほど管理が非常に難しくなる
- 自動翻訳システムが混乱する可能性がある
常に原文(通常は英語)を基準に、概念/役割を短く説明 するのがベストです。
いつ Contextual Markers を使うべきか?
次のいずれかに該当する場合、ほぼ必ず context/pgettext を検討してください。
- 短い文字列(1–2語)
- ボタンテキスト
- タブ名
- メニュー項目
- 実際の UI で異なる意味で再利用される文字列
"Open","Close","Save","Apply","Reset"など共通アクション"Book","Order","Post"のように名詞/動詞として使われる単語
- 翻訳者が画面を見ずに翻訳するケース
- 翻訳プラットフォームを使用する時
- 外部翻訳業者に
.poファイルだけを渡す時
まとめ

- Django はデフォルトで同一
msgidに一つの翻訳しかマッピングしません。 - そのため
"Polish","May","Book"のように 同音異義語 が翻訳でよく絡みます。 -
このとき ソース文字列を無理に変えずに:
-
テンプレートでは
{% translate "…" context "…" %} -
Python コードでは
pgettext/pgettext_lazy/npgettextを使い、文脈(context) を追加 -
すると
.poファイルにmsgctxtが追加され、 - 同じ原文でも別々の翻訳が可能
- 翻訳ツールでも別項目として管理できる
結果として:
- コードの可読性は保ちつつ
- 翻訳品質と保守性を大幅に向上
多言語サポートがますます必須になる時代に、 Contextual Markers は Django 開発者が一度は習得しておくべき i18n ツールです。
おすすめの関連記事
コメントはありません。