شما رشتهها را برای ترجمه علامتگذاری کردید، فایلهای .po را تولید کردید، compilemessages را اجرا کردید و برنامهتان همچنان انگلیسی نشان میدهد. تنها نیستید. فریمورک i18n جنگو قدرتمند است، اما لبههای تیزی دارد که حتی توسعهدهندگان باتجربه را هم گرفتار میکند.
این راهنما ۱۰ دلیل رایج شکست بیصدای ترجمههای Django را با علائم و راهحلهای دقیق برای هر کدام پوشش میدهد.
۱. فراموش کردن اجرای compilemessages پس از ویرایش فایلهای .po
یک فایل .po را ویرایش کردید (به صورت دستی یا با ابزار)، اما متن ترجمه شده هرگز ظاهر نمیشود. برنامه همچنان رشتههای اصلی انگلیسی را نشان میدهد.
Django فایلهای .po را در زمان اجرا نمیخواند. در عوض فایلهای باینری .mo (machine object) کامپایل شده را میخواند. اگر فایل .po را بدون کامپایل مجدد ویرایش کنید، Django از تغییرات بیخبر است.
بعد از هر تغییر فایل .po، دستور compilemessages را اجرا کنید:
python manage.py compilemessages
اگر ترجمههایتان را با TranslateBot خودکار میکنید، compilemessages را به عنوان آخرین مرحله در جریان کارتان اضافه کنید:
python manage.py makemessages -a --no-obsolete
python manage.py translate
python manage.py compilemessages
۲. نبود {% load i18n %} در قالبها
در یک قالب از {% trans "Hello" %} استفاده میکنید، اما Django خطای TemplateSyntaxError میدهد. یا بدتر، اگر موتور قالب اشتباه پیکربندی شده باشد، تگ بیصدا هیچ کاری نمیکند.
تگهای {% trans %} و {% blocktrans %} در کتابخانه تگ قالب i18n جنگو قرار دارند. بدون بارگذاری آن، موتور قالب آنها را نمیشناسد.
{% load i18n %} را در بالای هر قالبی که از تگهای ترجمه استفاده میکند اضافه کنید:
{% load i18n %}
<h1>{% trans "Welcome to our site" %}</h1>
<p>{% blocktrans with name=user.name %}Hello, {{ name }}!{% endblocktrans %}</p>
این یک الزام برای هر قالب است. حتی اگر قالب والد i18n را بارگذاری کند، قالبهای فرزندی که از تگهای ترجمه استفاده میکنند به اعلان {% load i18n %} خودشان نیاز دارند.
۳. LocaleMiddleware در MIDDLEWARE نیست یا در جایگاه اشتباه قرار دارد
Django همیشه بدون توجه به هدر Accept-Language مرورگر، پیشوند URL یا تنظیمات نشست، محتوا را به زبان پیشفرض ارائه میدهد.
LocaleMiddleware زبان فعال را برای هر درخواست تعیین میکند. بدون آن، Django به LANGUAGE_CODE پیشفرض برمیگردد و تمام مکانیسمهای انتخاب زبان را نادیده میگیرد. موقعیت آن در پشته middleware نیز مهم است، زیرا به دادههای نشست و تفکیک URL نیاز دارد.
LocaleMiddleware را به تنظیمات MIDDLEWARE خود، بعد از SessionMiddleware و CommonMiddleware اضافه کنید:
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.locale.LocaleMiddleware", # Must be after SessionMiddleware
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
]
همچنین اگر از تغییر زبان مبتنی بر URL استفاده میکنید، مطمئن شوید django.conf.urls.i18n در پیکربندی URL شما گنجانده شده است:
from django.conf.urls.i18n import i18n_patterns
urlpatterns = i18n_patterns(
path("", include("myapp.urls")),
)
۴. عدم تطابق کد زبان (مثلاً pt-br در مقابل pt_BR)
ترجمهها در فایلهای .po شما وجود دارند، compilemessages موفق است، اما Django ترجمه را برای برخی زبانها نادیده میگیرد.
Django انتظار دارد دایرکتوریهای زبان از قالب <language>_<COUNTRY> با جداکننده زیرخط و کد کشور با حروف بزرگ پیروی کنند. برای مثال، pt_BR برای پرتغالی برزیلی. اگر دایرکتوری شما pt-br، pt-BR یا ptBR نامگذاری شده باشد، Django آن را پیدا نخواهد کرد. همین موضوع برای تنظیمات LANGUAGES نیز صدق میکند: کدهای آنجا از خط تیره (pt-br) استفاده میکنند، اما فایل سیستم از زیرخط (pt_BR) استفاده میکند.
مطمئن شوید ساختار دایرکتوری شما با انتظارات Django مطابقت دارد:
locale/
pt_BR/
LC_MESSAGES/
django.po
django.mo
و در تنظیمات خود، از فرم خطتیرهدار استفاده کنید:
LANGUAGES = [
("en", "English"),
("pt-br", "Brazilian Portuguese"),
("zh-hans", "Simplified Chinese"),
]
هنگام اجرای makemessages، از فرم زیرخطدار برای پرچم زبان استفاده کنید:
python manage.py makemessages -l pt_BR
۵. ورودیهای Fuzzy که بیصدا در حین کامپایل رد میشوند
یک ترجمه در فایل .po وجود دارد، اما Django رشته اصلی انگلیسی را در زمان اجرا برای آن ورودی خاص نشان میدهد. این خصوصاً ناامیدکننده است زیرا ترجمه دقیقاً همانجا در فایل موجود است.
وقتی makemessages جنگو تشخیص دهد که یک رشته مبدأ کمی تغییر کرده، ترجمه موجود را به عنوان "fuzzy" (به معنای حدسی که نیاز به بررسی انسانی دارد) علامتگذاری میکند. دستور compilemessages تمام ورودیهای fuzzy را رد میکند و آنها را ترجمهنشده در نظر میگیرد. بنابراین ورودی در فایل .po ترجمهشده به نظر میرسد، اما فایل .mo آن را کاملاً حذف میکند.
یک ورودی fuzzy اینگونه به نظر میرسد:
#, fuzzy
msgid "Welcome to our website!"
msgstr "Welkom op onze website!"
ترجمه را بررسی کنید، در صورت نیاز msgstr را بهروزرسانی کنید، سپس پرچم #, fuzzy را حذف کنید:
msgid "Welcome to our website!"
msgstr "Welkom op onze website!"
سپس مجدداً کامپایل کنید:
python manage.py compilemessages
در پروژههای بزرگتر، ورودیهای fuzzy انباشته میشوند و به راحتی از قلم میافتند. دستور check_translations در TranslateBot اینها را به صورت خودکار شناسایی میکند:
python manage.py check_translations
locale/nl/LC_MESSAGES/django.po: 0 untranslated, 3 fuzzy
CommandError: Translation check failed
check_translations --makemessages را به خط لوله CI خود اضافه کنید و دیگر هرگز یک ورودی fuzzy ارسال نخواهید کرد.
۶. LOCALE_PATHS پیکربندی نشده یا به دایرکتوری اشتباه اشاره میکند
makemessages فایلهای .po را در یک مکان ایجاد میکند، اما Django آنها را جای دیگری جستجو میکند. ترجمهها روی دیسک وجود دارند اما هرگز بارگذاری نمیشوند.
Django فایلهای ترجمه را به ترتیب خاصی جستجو میکند: ابتدا دایرکتوریهای LOCALE_PATHS، سپس دایرکتوری locale/ هر برنامه، و در نهایت دایرکتوری locale/ پروژه. اگر LOCALE_PATHS تنظیم نشده باشد یا به مسیر اشتباه اشاره کند، Django ممکن است هرگز فایلهای .po شما را پیدا نکند.
LOCALE_PATHS را در تنظیمات خود به یک مسیر مطلق تنظیم کنید:
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
LOCALE_PATHS = [
BASE_DIR / "locale",
]
بررسی کنید که دایرکتوری وجود داشته باشد و ساختار مورد انتظار را داشته باشد:
locale/
de/
LC_MESSAGES/
django.po
django.mo
nl/
LC_MESSAGES/
django.po
django.mo
یک اشتباه رایج تنظیم LOCALE_PATHS به locale/ (نسبی) به جای مسیر مطلق است. Django مسیرهای نسبی را از ریشه پروژه شما حل نمیکند. به دایرکتوری کاری فرآیند بستگی دارد که اغلب آن چیزی نیست که انتظار دارید.
۷. کش ترجمههای قدیمی را ارائه میدهد
ترجمهها را بهروزرسانی و کامپایل کردید، اما متن قدیمی همچنان ظاهر میشود. راهاندازی مجدد سرور مشکل را حل میکند.
کاتالوگ ترجمه Django در هر فرآیند یک بار بارگذاری میشود. در محیط تولید، سرورهای WSGI/ASGI مانند Gunicorn یا Uvicorn فرآیندهای کارگر را برای مدت طولانی زنده نگه میدارند. فایل .mo ممکن است روی دیسک تغییر کرده باشد، اما فرآیند در حال اجرا همچنان ترجمههای قدیمی را در حافظه دارد. علاوه بر آن، اگر از فریمورک کش Django یا یک پروکسی معکوس مانند Nginx یا Cloudflare استفاده میکنید، پاسخهای کش شده محتوای قدیمی را تا زمان انقضا ارائه خواهند داد.
پس از استقرار ترجمههای جدید، سرور برنامه خود را مجدداً راهاندازی کنید:
# Gunicorn
kill -HUP $(cat /tmp/gunicorn.pid)
# Systemd
sudo systemctl restart myapp
# Docker
docker compose restart web
برای فریمورک کش Django، پس از بهروزرسانی ترجمهها کش را پاک کنید:
from django.core.cache import cache
cache.clear()
در محیط توسعه، runserver هنگام تغییر فایلهای Python به صورت خودکار بارگذاری مجدد میکند اما فایلهای .mo را زیر نظر نمیگیرد. پس از اجرای compilemessages باید آن را به صورت دستی مجدداً راهاندازی کنید.
۸. رشتههایی که در gettext (_() یا {% trans %}) قرار نگرفتهاند
makemessages رشتههای خاصی را استخراج نمیکند، بنابراین آنها هرگز در فایلهای .po شما ظاهر نمیشوند و هرگز ترجمه نمیشوند. این ابتداییترین مشکل و همچنین آسانترین برای نادیده گرفتن در یک پایگاه کد بزرگ است.
دستور makemessages جنگو از xgettext برای اسکن کد منبع شما برای نشانگرهای ترجمه استفاده میکند. اگر یک رشته در gettext() (معمولاً با نام مستعار _()), gettext_lazy(), {% trans %} یا {% blocktrans %} قرار نگرفته باشد، برای فرآیند استخراج نامرئی است.
هر رشتهای که کاربر میبیند را بپوشانید:
# Python code
from django.utils.translation import gettext_lazy as _
class Article(models.Model):
class Meta:
verbose_name = _("article")
verbose_name_plural = _("articles")
# Views
from django.utils.translation import gettext as _
def my_view(request):
message = _("Your changes have been saved.")
return HttpResponse(message)
<!-- Templates -->
{% load i18n %}
<h1>{% trans "Welcome" %}</h1>
<p>{% blocktrans with count=items|length %}You have {{ count }} items.{% endblocktrans %}</p>
از پرچم --dry-run TranslateBot برای پیشنمایش رشتههایی که در حال حاضر ترجمهنشدهاند استفاده کنید تا رشتههایی که در حین استخراج از قلم افتادهاند را شناسایی کنید:
python manage.py translate --target-lang de --dry-run
این تمام ورودیهای ترجمهنشده در فایلهای .po شما را بدون هیچ فراخوانی API یا تغییری نشان میدهد.
۹. f-string ها قابل ترجمه نیستند (محدودیت Django)
یک f-string را در _() قرار میدهید و یا خطای نحوی دریافت میکنید، یا makemessages یک رشته شکسته/ناقص استخراج میکند که قابل ترجمه نیست.
f-string های Python در زمان اجرا ارزیابی میشوند. ابزار استخراج xgettext کد منبع را به صورت ایستا تجزیه میکند، بنابراین نمیتواند عبارات Python درون آکولاد {} را ارزیابی کند. این بدان معناست که _(f"Hello, {name}") به عنوان رشتهای حاوی عبارت لفظی {name} استخراج میشود (یا کلاً استخراج نمیشود) و ورودی .po حاصل هرگز با رشته زمان اجرا مطابقت نخواهد داشت.
در عوض از قالببندی % جنگو یا .format() با جایگاهنماهای نامدار استفاده کنید:
# Wrong -- f-string cannot be extracted
message = _(f"Hello, {user.name}! You have {count} new messages.")
# Correct -- named placeholders
message = _("Hello, %(name)s! You have %(count)d new messages.") % {
"name": user.name,
"count": count,
}
# Also correct -- .format() with positional args
message = _("Hello, {0}! You have {1} new messages.").format(user.name, count)
این محدودیت TranslateBot یا ابزار نیست. اساسی نحوه کار gettext است. رشته مبدأ باید یک لفظ ایستا باشد تا بتواند استخراج و در زمان اجرا جستجو شود.
TranslateBot تمام این قالبهای جایگاهنما (%(name)s، {0}، %s، تگهای HTML) را در حین ترجمه حفظ میکند، بنابراین رشتههای ترجمه شده کاملاً کاربردی باقی میمانند.
۱۰. خطاهای رشته قالب جایگاهنما که کامپایل .po را خراب میکنند
compilemessages با خطا شکست میخورد، یا فایل .po عدم تطابق پرچم #, python-format دارد و ورودی بیصدا حذف میشود.
وقتی یک رشته مبدأ حاوی جایگاهنماهای قالب Python مانند %(name)s باشد، Django ورودی .po را با #, python-format علامتگذاری میکند. اگر ترجمه جایگاهنماهای متفاوتی داشته باشد (غلط املایی مانند %(nome)s، جایگاهنمای گمشده یا اضافی) ابزارهای gettext ممکن است ورودی را رد کنند یا compilemessages شکست بخورد. این معمولاً با ترجمههای دستی یا با ابزارهای ترجمه هوش مصنوعی که معنای جایگاهنما را درک نمیکنند اتفاق میافتد.
یک ورودی خراب اینگونه به نظر میرسد:
#, python-format
msgid "Hello, %(name)s! You have %(count)d new messages."
msgstr "Hallo, %(naam)s! Je hebt %(count)d nieuwe berichten."
اینجا %(naam)s باید %(name)s باشد. جایگاهنماها باید دقیقاً با مبدأ مطابقت داشته باشند.
اطمینان حاصل کنید رشتههای ترجمه شده دقیقاً همان جایگاهنماهای مبدأ را دارند. غلطهای املایی، جایگاهنماهای گمشده و جایگاهنماهای اضافی را بررسی کنید.
این یکی از حوزههایی است که TranslateBot مزیت واقعی ارائه میدهد. منطق حفظ جایگاهنمای آن تضمین میکند که تمام رشتههای قالب (%(name)s، {0}، %s) در خروجی ترجمه شده دقیقاً با مبدأ مطابقت دارند. مدیریت جایگاهنما با ۱۰۰٪ پوشش تست پوشانده شده است، بنابراین خطاهای رشته قالب ناشی از ترجمه به جای گرفتار شدن در زمان کامپایل، در سطح ابزار حذف میشوند.
اگر به صورت دستی ترجمه میکنید یا از ابزاری استفاده میکنید که جایگاهنماها را مدیریت نمیکند، فایلهای .po خود را با این دستور اعتبارسنجی کنید:
msgfmt --check-format locale/de/LC_MESSAGES/django.po
این اعتبارسنجی رشته قالب gettext را اجرا میکند و هرگونه عدم تطابق را گزارش میدهد.
جمعبندی: یک جریان کار دفاعی
بیشتر این مشکلات یک علت ریشهای مشترک دارند: مراحل دستی که فراموش کردنشان آسان است. اینجا جریان کاری است که از هر ۱۰ مشکل جلوگیری میکند:
# 1. Extract strings (catches #8 -- any new gettext-wrapped strings)
python manage.py makemessages -a --no-obsolete
# 2. Translate (catches #1, #5, #8, #9, #10 -- handles untranslated,
# fuzzy, and placeholder issues automatically)
python manage.py translate
# 3. Compile (catches #1 -- generates .mo files)
python manage.py compilemessages
# 4. Verify in CI (catches everything that slipped through)
python manage.py check_translations --makemessages
مرحله ۴ را به خط لوله CI خود اضافه کنید و رشتههای ترجمهنشده، ورودیهای fuzzy و خطاهای قالب قبل از رسیدن به تولید، ساخت را با شکست مواجه خواهند کرد.
جدول مرجع سریع
| علت | علامت | راهحل یک خطی |
|---|---|---|
بدون compilemessages |
ترجمهها وجود دارند اما ظاهر نمیشوند | python manage.py compilemessages |
نبود {% load i18n %} |
TemplateSyntaxError روی {% trans %} |
{% load i18n %} را به قالب اضافه کنید |
| نبود LocaleMiddleware | زبان همیشه پیشفرض انگلیسی است | django.middleware.locale.LocaleMiddleware را به MIDDLEWARE اضافه کنید |
| عدم تطابق کد زبان | دایرکتوری زبان پیدا نشد | برای دایرکتوریها از pt_BR (زیرخط) و برای تنظیمات از pt-br (خط تیره) استفاده کنید |
| رد شدن ورودیهای fuzzy | ترجمه در .po هست اما در برنامه نیست |
بعد از بررسی پرچم #, fuzzy را حذف کنید |
| LOCALE_PATHS اشتباه | فایلهای .po وجود دارند اما Django آنها را نادیده میگیرد |
LOCALE_PATHS را به مسیر مطلق تنظیم کنید |
| ترجمههای کش شده | متن قدیمی پس از بهروزرسانی ظاهر میشود | سرور برنامه را مجدداً راهاندازی کنید |
| رشته در gettext نیست | رشته از فایلهای .po گم شده |
در _() یا {% trans %} بپوشانید |
| f-string در gettext | استخراج خراب یا عدم تطابق زمان اجرا | با جایگاهنماهای % یا .format() جایگزین کنید |
| عدم تطابق جایگاهنما | شکست compilemessages یا حذف ورودی |
جایگاهنماها را بین مبدأ و ترجمه دقیقاً مطابقت دهید |
بیشتر این مشکلات وقتی مرحله ترجمه را خودکار کنید و بررسیها را در CI اجرا کنید ناپدید میشوند. دستور translate در TranslateBot حفظ جایگاهنما و ترجمه تدریجی را مدیریت میکند، در حالی که check_translations هر چیزی که از لا به لا رد شده را (ورودیهای ترجمهنشده، پرچمهای fuzzy و مشکلات رشته قالب) قبل از رسیدن به تولید شناسایی میکند.