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

مهاجرت

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

پرسش پاسخ
آیا فایل‌های .po و .moِ موجود هنوز کار می‌کنند؟ بله. همان فایل‌ها، همان ابزارها.
آیا فراخوانی‌های قدیم و جدید می‌توانند در یک فایل زندگی کنند؟ بله، و یک نگاشت استخراج‌کننده هر دو را می‌پوشاند.
آیا msgid تغییر می‌کند؟ از .format() نه. از قالب % بله.
آیا کل پروژه باید یک‌باره جابه‌جا شود؟ نه. یک محل فراخوانی هم تغییری معتبر است.
Jinja و قالب‌های Django و JavaScript چه می‌شوند؟ دست‌نخورده، با همان کاتالوگ‌ها.

باقی این صفحه، جزئیاتِ پشت هر یک از این‌هاست.

از .format(): msgid تغییر نمی‌کند

این همان حالتی است که مهاجرت در آن تقریباً هیچ هزینه‌ای ندارد. یک پیام str.format و یک پیام t-string همان کلید کاتالوگ را می‌سازند، چون کلید در هر دو حالت متنی است که {name} در آن باقی مانده است:

# Before
_("Hello {name}").format(name=name)

# After — the msgid is still "Hello {name}"
tr(t"Hello {name}")

پس ترجمهٔ موجود همچنان به پیام چسبیده می‌ماند. از کاتالوگی آغاز کنید که این را دارد

#: app.py:6
#, python-brace-format
msgid "Hello {name}"
msgstr "こんにちは {name}"

فراخوانی را تغییر دهید، دوباره استخراج کنید و به‌روزرسانی کنید:

$ pybabel extract -F babel.cfg -o locales/messages.pot .
extracting messages from app.py (encoding="utf-8")
writing PO template file to locales/messages.pot
$ pybabel update -i locales/messages.pot -d locales
updating catalog locales/ja/LC_MESSAGES/messages.po based on locales/messages.pot

مدخلی که بازمی‌گردد تنها در دو خط فراداده فرق دارد و بس — یک توضیحِ نشانه که آن را پیامی t-string معرفی می‌کند، و یک شمارهٔ خط مبدأ:

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

نه پرچم fuzzy، نه ترجمهٔ دوباره، در هیچ زبانی. پیام بی‌درنگ رندر می‌شود:

$ pybabel compile -d locales
compiling catalog locales/ja/LC_MESSAGES/messages.po to locales/ja/LC_MESSAGES/messages.mo
$ python app.py
こんにちは Ada

update --check کاتالوگ‌ها را عقب‌افتاده گزارش می‌کند

همان توضیحِ نشانه و شماره‌های خطِ جابه‌جاشده برای این کافی‌اند که pybabel update --check بگوید کاتالوگ نیاز به بازتولید دارد، چون کل مدخل را مقایسه می‌کند نه فقط ترجمه را. pybabel updateِ واقعی را در همان کامیتِ تغییر کد اجرا کنید و کاتالوگ‌ها را با آن کامیت کنید — همان عادتی که دروازهٔ CI پیش‌تر می‌خواهد.

از قالب %: msgid تغییر می‌کند، پس ترجمه‌ها fuzzy می‌شوند

نحو printf درون پیام زندگی می‌کند؛ پس جایگزین‌کردنش کلید کاتالوگ را بازنویسی می‌کند. راهی گرد آن نیست، و این هزینهٔ صادقانهٔ کنارگذاشتن %(name)s است:

# Before
_("Hello %(name)s") % {"name": name}

# After — a different msgid
tr(t"Hello {name}")

pybabel update پیام تازه را خویشاوندِ نزدیکِ پیام حذف‌شده می‌شناسد و ترجمهٔ قدیمی را با نشانِ fuzzy به آن می‌رساند:

#. gettext-tstrings
#: app.py:4
#, fuzzy, python-brace-format, python-format
msgid "Hello {name}"
msgstr "こんにちは %(name)s"

سه چیز دربارهٔ این وضعیت باید بدانید:

  • در زمان اجرا چیزی نمی‌شکند. مدخل‌های fuzzy از .moِ کامپایل‌شده کنار گذاشته می‌شوند؛ پس برنامه تا وقتی انسانی جفت را تأیید نکرده، پیام مبدأ را رندر می‌کند — همان تنزلی که هر پیامِ بازنویسی‌شده از سر می‌گذراند.
  • تا وقتی fuzzy‌اند، CI سبز می‌ماند. بررسی‌کنندهٔ جای‌نگهدار از مدخل‌های fuzzy می‌گذرد، دقیقاً همان‌طور که msgfmt --check-format می‌کند، چون مدخلی که نمی‌تواند به زمان اجرا برسد نباید بیلدی را بشکند. همان لحظه که مترجم پرچم را پاک کند، مدخل مانند هر مدخل دیگری بررسی می‌شود — پس %(name)sای که در ترجمه‌ای تأییدشده جا مانده باشد همان‌جا گرفته می‌شود، یعنی همان نقطه‌ای که وگرنه شروع به رندرشدن می‌کرد.
  • پرچم قدیمی python-format هم سوار می‌شود و باید همراه پرچم fuzzy حذف شود، وگرنه msgfmt --check-format قواعد printf را بر پیامی با قالب آکولادی اعمال خواهد کرد.

برای جای‌نگهدارهای نام‌دارِ printf این ویرایش مکانیکی است — %(name)s می‌شود {name} و هیچ چیز دیگری تکان نمی‌خورد — پس یک کاتالوگ بزرگ یک گذرِ اسکریپتی است و پس از آن بازبینی یک مترجم، نه ترجمه‌ای از نو. %sِ موضعی مکانیکی نیست: نامی ندارد که منتقل شود، و برگزیدن یک نام اصلاً نکتهٔ همین تغییر است.

بنابراین مهاجرت می‌تواند با هر سرعتی که بازبینی اجازه می‌دهد پیش برود: یک مدخلِ fuzzyِ تبدیل‌نشده کارِ نمایانی در کاتالوگ است، نه بیلدی شکسته.

هم‌زیستی فراخوانی‌های قدیم و جدید

استخراج‌کننده‌ای که t-string‌ها را می‌خواند فراخوانی‌های معمولی gettext را هم می‌خواند؛ پس یک نگاشت، فایلی را که میانهٔ مهاجرت است می‌پوشاند:

[gettext_tstrings: **.py]
encoding = utf-8
from gettext_tstrings import tr
from myapp.i18n import _

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

هر دو پیام در یک الگو فرود می‌آیند، و تنها پیامِ t-string توضیحِ نشانه‌ای را دارد که بررسی افزودهٔ این کتابخانه را روشن می‌کند:

#: app.py:5
msgid "Save changes"
msgstr ""

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

این استخراج‌کننده _()، چهار نام استاندارد gettext، نام‌های دیگر tr() و ntr() و صورت‌های معوق lazy_gettext() و lazy_pgettext() را می‌شناسد. کمکیِ خودتان باید در نگاشت نام برده شود.

در زمان اجرا این دو سبک به یک اندازه مستقل‌اند: gettext.translation() یک شیء ترجمه برمی‌گرداند و هم _ و هم نقطه‌های ورود این کتابخانه از همان می‌خوانند.

چه چیزی جابه‌جا نمی‌شود

  • زبان‌های قالب. {% trans %}ِ Jinja2، تگ‌های قالب Django و استخراج‌کننده‌های Babelِ آن‌ها بی‌تغییر کار می‌کنند و همان کاتالوگ‌های PO را تغذیه می‌کنند. t-string‌ها نحو پایتون‌اند؛ به کد مبدأ پایتون مربوط‌اند.
  • فایل‌های کاتالوگ شما. نه تغییر قالب، نه فایل تازه، نه گام تبدیل.
  • پلتفرم ترجمهٔ شما. تبادلِ .po یکسان است، و پرچم python-brace-format که یک پیام t-string دارد همان پرچمی است که یک پیام .format() دارد — پس QA جای‌نگهدار به کار خود ادامه می‌دهد.
  • کدِ غیرپایتونی. یک کاتالوگ JavaScript یا C در همان پروژه بی‌اثر می‌ماند.

چک‌لیستی برای مهاجرت

  1. افزونهٔ babel را آن‌جا که pybabel اجرا می‌شود بیفزایید و نگاشت python را در babel.cfg به روش gettext_tstrings تغییر دهید — از آن پس یک نگاشت هر دو سبک را می‌پوشاند و -k برای فراخوانی‌های معمولی همچنان کار می‌کند.
  2. نخست محل‌های فراخوانی .format() را تبدیل کنید. دوباره استخراج کنید، pybabel update را اجرا کنید و کاتالوگ‌ها را همراه کد کامیت کنید؛ انتظار هیچ مدخل fuzzy نداشته باشید.
  3. محل‌های فراخوانی قالب % را در دسته‌هایی تبدیل کنید که بتوانید بازبینی‌شان کنید، و جای‌نگهدارهای منتقل‌شده را بازنویسی و پرچم‌های fuzzy و python-format را پاک کنید.
  4. آنچه را محدودیت رد می‌کند اصلاح کنید: یک درون‌یابی باید نامی ساده باشد؛ پس t"Hello {user.name}" نخست به یک متغیر محلی تبدیل می‌شود. این ویرایشی در محل فراخوانی است، نه در کاتالوگ.
  5. وقتی این گذر تمام شد، strict = true را در نگاشت استخراج‌کننده روشن کنید تا پیامی که نمی‌شود استخراجش کرد به‌جای ناپدیدشدن از الگو، بیلد را بشکند.
  6. بررسی زمان اجرا را از در محیط عملیاتی بیفزایید: به‌ازای هر زبانِ روانه‌شده یک پیام را از یک Translatorِ سخت‌گیر بگذرانید.

گام‌های ۲ و ۳ کامیت‌های معمولی‌اند. هیچ چیز در این فهرست به یک روزِ تعویضِ یک‌بارهٔ همه‌چیز نیاز ندارد.