پرش به محتویات

آموزش

این صفحه از یک پوشهٔ خالی آغاز می‌کند و به برنامه‌ای می‌رسد که به ژاپنی خوش‌آمد می‌گوید. پنج گام، بدون پیش‌فرضِ هیچ تجربه‌ای با gettext، و هر فرمان همراه با خروجی‌ای که واقعاً تولید می‌کند نشان داده می‌شود — تا در هر گام بدانید در مسیر درست هستید یا نه.

به پایتون 3.14 یا جدیدتر نیاز دارید، چون t-string نحو تازه‌ای در 3.14 است. زبان مقصدِ نمونهٔ این صفحه ژاپنی است، اما هیچ‌چیز به این انتخاب وابسته نیست. برای استفاده از زبانی دیگر، ja را در گام ۴ عوض کنید — همان کد محلی تنها جایی است که این زبان را نام می‌برد.

۱. نصب

python -m pip install "gettext-tstrings[babel]"

افزونهٔ [babel] ابزار Babel را می‌آورد؛ همان ابزاری که در گام ۳ پیام‌های شما را در فایل‌های کاتالوگ گرد می‌آورد. این یک ابزار زمانِ توسعه است: کد عملیاتی تنها با کتابخانهٔ استاندارد رندر می‌کند.

۲. علامت‌گذاری یک پیام در کد

فایل app.py را بسازید:

from gettext_tstrings import tr

name = "Ada"
print(tr(t"Hello {name}"))

t"Hello {name}" شبیه یک f-string است، اما پیشوند t متن و مقدار را به‌جای ادغام درجا، جدا از هم نگه می‌دارد. همین جدایی است که به tr() اجازه می‌دهد ترجمهٔ کل جملهٔ Hello {name} را جست‌وجو کند و مقدار را پس از آن جای‌گذاری کند.

همین حالا اجرایش کنید:

$ python app.py
Hello Ada

هنوز هیچ ترجمه‌ای نصب نشده، پس متن مبدأ همان‌طور که هست رندر می‌شود. برنامه‌ای که از این کتابخانه استفاده می‌کند هرگز برای اجرا نیازمند کاتالوگ نیست — انگلیسی (یا هر زبان مبدأ شما) پشتیبانِ درونی است.

۳. استخراج پیام‌ها

مترجم‌ها معمولاً به‌جای کد مبدأ از کاتالوگ‌ها کار می‌کنند، پس فایل کوچکی به نام کاتالوگ میان شما و آن‌ها رفت‌وآمد می‌کند. نخستین گام برای ساختن آن، گرد آوردن همهٔ پیام‌های علامت‌گذاری‌شده از دل کد است.

با ساختن babel.cfg به Babel بگویید پیام‌هایتان را چگونه بیابد:

[gettext_tstrings: **.py]
encoding = utf-8

سپس آن‌ها را در یک فایل الگو (.pot) استخراج کنید:

$ mkdir -p locales
$ pybabel extract -F babel.cfg -c "Translators:" -o locales/messages.pot .
extracting messages from app.py (encoding="utf-8")
writing PO template file to locales/messages.pot

اکنون locales/messages.pot برای هر پیام یک مدخل دارد:

#. gettext-tstrings
#: app.py:4
#, python-brace-format
msgid "Hello {name}"
msgstr ""

msgid کلیدی است که کد شما جست‌وجو خواهد کرد. msgstrِ خالی جای ترجمه است — اما نه در این فایل: .pot یک الگو است و گام بعدی برای هر زبان یک نسخه از آن می‌گیرد.

۴. ترجمه و کامپایل

کاتالوگ ژاپنی را از روی الگو بسازید:

$ pybabel init -i locales/messages.pot -d locales -l ja
creating catalog locales/ja/LC_MESSAGES/messages.po based on locales/messages.pot

فایل locales/ja/LC_MESSAGES/messages.po را باز کنید و msgstr را پر کنید:

msgid "Hello {name}"
msgstr "こんにちは {name}"

{name} را دقیقاً همان‌طور که هست نگه دارید — جای‌نگهدار همان راهی است که مقدار از آن جای خود را در جملهٔ ترجمه‌شده پیدا می‌کند، و ترجمه آزاد است آن را هر جا که زبان مقصد لازم دارد جابه‌جا کند. در یک پروژهٔ واقعی همین فایل .po است که به مترجم می‌سپارید یا به پلتفرم ترجمه بارگذاری می‌کنید؛ قالب در هر دو حالت یکی است.

کاتالوگ‌ها به شکل متن ویرایش می‌شوند اما به شکل دودویی (.mo) بار می‌شوند؛ پس کامپایل کنید:

$ pybabel compile -d locales
compiling catalog locales/ja/LC_MESSAGES/messages.po to locales/ja/LC_MESSAGES/messages.mo

این فرمان یک تور ایمنی هم هست. اگر ترجمه جای‌نگهدار را خراب کرده بود — مثلاً {nome} به‌جای {name} — از پذیرفتن آن سر باز می‌زد:

$ pybabel compile -d locales
error: locales/ja/LC_MESSAGES/messages.po:24: translation does not match the
source placeholders: {name} is missing; {nome} is not in the source message
1 errors encountered.

یک نکتهٔ درخور دانستن از همین حالا: خطا را گزارش می‌کند و با کد ناصفر بیرون می‌آید، اما .mo را به هر حال می‌نویسد. در یک پروژهٔ واقعی این CI است که باید بر آن کد خروج بایستد — در محیط عملیاتی همین را برپا می‌کند.

۵. اجرا

گام‌های ۲ تا ۴ از tr() استفاده می‌کردند که دنبال کاتالوگ می‌گردد و هیچ نمی‌یابد. حالا که کاتالوگی هست، آن را بار کنید و یک‌بار مقید کنید: Translator کاتالوگ را نگه می‌دارد تا محل‌های فراخوانی مجبور نباشند نامش را ببرند، و _ نام مرسوم gettext برای نتیجه است.

app.py را به کاتالوگ کامپایل‌شده وصل کنید. روی نشانگرها کلیک کنید تا ببینید هر خط چه می‌کند:

import gettext

from gettext_tstrings import Translator

_ = Translator(gettext.translation("messages", localedir="locales", languages=["ja"]))  # (1)!

name = "Ada"
print(_(t"Hello {name}"))  # (2)!
  1. کتابخانهٔ استاندارد .moِ کامپایل‌شده را بار می‌کند و Translator آن را به یک فراخوانی‌پذیر می‌بندد. _ نام مرسوم gettext برای «این را ترجمه کن» است — کوتاه، چون روی هر رشتهٔ رو به کاربر ظاهر می‌شود. این همان ترجمه‌ای را انجام می‌دهد که tr می‌کرد، بسته‌شده به یک کاتالوگ.
  2. در لحظهٔ فراخوانی: متن t-string به کلید جست‌وجوی Hello {name} تبدیل می‌شود، کاتالوگ پاسخ こんにちは {name} را می‌دهد، پاسخ در برابر جای‌نگهدارهای مبدأ بررسی می‌شود، و تنها پس از آن مقدار جای‌گذاری می‌شود.
$ python app.py
こんにちは Ada

این کل چرخه است، و ارزشش را دارد که یک‌جا در یک تصویر دیده شود:

flowchart LR
  mark["۱–۲ علامت‌گذاری<br>t-string در کد"] --> extract["۳ استخراج<br>messages.pot"]
  extract --> translate["۴ ترجمه<br>ja/…/messages.po"]
  translate --> compile["۴ کامپایل<br>ja/…/messages.mo"]
  compile --> run["۵ اجرا<br>こんにちは Ada"]

علامت‌گذاری ← استخراج ← ترجمه ← کامپایل ← اجرا. هر چیز دیگری در این وب‌گاه، پرداختی بر یکی از همین پنج گام است.

گام‌های بعدی

  • چرا t-string؟ — این طراحی از چه چیزی محافظت می‌کند، در مقایسه با %(name)s و .format() و رشته‌های $.
  • راهنما — صورت‌های جمع، زبانِ هر درخواست، رشته‌های معوق، و آنچه در زمان اجرا رخ می‌دهد وقتی کاتالوگ به هر حال خراب باشد.
  • در محیط عملیاتی — همین چرخه آن‌گونه که یک تیم هفته به هفته می‌گرداند: به‌روزرسانی کاتالوگ‌ها، دروازه‌های CI، و پلتفرم‌های ترجمه.
  • استخراج — مرجع کامل pybabel: نام‌های تابع سفارشی، حالت سخت‌گیرانه در CI، و بررسی‌هایی که از کاتالوگ‌هایتان پاسداری می‌کنند.
  • مهاجرت — اگر پروژه‌ای که واقعاً می‌خواهید این کار را در آن بکنید، از پیش کاتالوگ‌های gettext دارد.
  • برای مترجمان — تنها صفحه‌ای که باید به دست کسی بدهید که آن خط‌های msgstr را پر می‌کند.