ترجمهٔ ماژول‌ها

این بخش توضیح می‌دهد چگونه قابلیت‌های ترجمه را برای ماژول خود فراهم کنید.

توجه

اگر می‌خواهید در ترجمهٔ خود Odoo مشارکت کنید، لطفاً به صفحهٔ ویکی Odoo مراجعه کنید.

صدور اصطلاحات قابل ترجمه

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

صدور ترجمه‌ها از طریق رابط مدیریتی با ورود به رابط backend و باز کردن Settings ↤ Translations ↤ Import / Export ↤ Export Translations انجام می‌شود

  • زبان را در پیش‌فرض (زبان جدید/قالب خالی) بگذارید

  • فرمت PO File را انتخاب کنید

  • ماژول خود را انتخاب کنید

  • روی Export کلیک کنید و فایل را دانلود کنید

../../_images/po-export.png

این به شما فایلی به نام yourmodule.pot می‌دهد که باید به دایرکتوری yourmodule/i18n/ منتقل شود. این فایل یک PO Template است که به‌سادگی رشته‌های قابل ترجمه را فهرست می‌کند و از آن می‌توان ترجمه‌های واقعی (فایل‌های PO) ساخت. فایل‌های PO را می‌توان با msginit، با یک ابزار اختصاصی ترجمه مانند POEdit یا با کپی کردن سادهٔ قالب به یک فایل جدید به نام language.po ایجاد کرد. فایل‌های ترجمه باید در yourmodule/i18n/، در کنار yourmodule.pot قرار گیرند و وقتی زبان متناظر نصب شد (از طریق Settings ↤ Translations ↤ Languages)، به‌طور خودکار توسط Odoo بارگذاری می‌شوند

توجه

ترجمه‌ها برای همهٔ زبان‌های بارگذاری‌شده نیز هنگام نصب یا به‌روزرسانی یک ماژول، نصب یا به‌روزرسانی می‌شوند

صادرات ضمنی

Odoo به‌طور خودکار رشته‌های قابل ترجمه را از محتوای نوع «داده» صادر می‌کند:

  • در نماهای غیر QWeb، همهٔ گره‌های متنی و همچنین محتوای ویژگی‌های string، help، sum، confirm و placeholder صادر می‌شوند

  • قالب‌های QWeb (هم سمت سرور و هم سمت کلاینت)، همهٔ گره‌های متنی صادر می‌شوند به‌جز داخل بلوک‌های t-translation="off"، محتوای ویژگی‌های title، alt، label و placeholder نیز صادر می‌شوند

  • برای Field، مگر اینکه مدلشان با _translate = False علامت‌گذاری شده باشد:

    • ویژگی‌های string و help آن‌ها صادر می‌شوند

    • اگر selection وجود داشته باشد و یک لیست (یا تاپل) باشد، صادر می‌شود

    • اگر ویژگی translate آن‌ها روی True تنظیم شده باشد، تمام مقادیر موجود آن‌ها (در همهٔ رکوردها) صادر می‌شوند

صادرات صریح

وقتی پای موقعیت‌های «دستوری‌تر» در کد Python یا کد Javascript به میان می‌آید، Odoo نمی‌تواند اصطلاحات قابل ترجمه را به‌طور خودکار صادر کند، بنابراین آن‌ها باید برای صادرات به‌صراحت علامت‌گذاری شوند. این کار با پیچاندن یک رشتهٔ literal در یک فراخوانی تابع انجام می‌شود.

در Python، تابع پیچاننده odoo.api.Environment._() و odoo.tools.translate._() است:

title = self.env._("Bank Accounts")

# old API for backward-compatibility
from odoo.tools import _
title = _("Bank Accounts")

در JavaScript، تابع پیچاننده معمولاً odoo.web._t() است:

title = _t("Bank Accounts");

هشدار

فقط رشته‌های literal را می‌توان برای صادرات علامت‌گذاری کرد، نه عبارات یا متغیرها. برای موقعیت‌هایی که رشته‌ها فرمت‌بندی می‌شوند، این به این معنی است که رشتهٔ قالب باید علامت‌گذاری شود، نه رشتهٔ فرمت‌شده

نسخهٔ lazy از _ و _t، کارخانهٔ odoo.tools.translate.LazyTranslate در python و odoo.web._lt() در javascript است. جستجوی ترجمه تنها هنگام رندر اجرا می‌شود و می‌تواند برای اعلان ویژگی‌های قابل ترجمه در متدهای کلاس متغیرهای سراسری استفاده شود.

from odoo.tools import LazyTranslate
_lt = LazyTranslate(__name__)
LAZY_TEXT = _lt("some text")

توجه

ترجمه‌های یک ماژول به‌صورت پیش‌فرض به فرانت‌اند عرضه نمی‌شوند و بنابراین از JavaScript قابل دسترسی نیستند. برای دستیابی به این هدف، نام ماژول باید یا با website پیشوند داشته باشد (درست مانند website_sale، website_event و غیره) یا با پیاده‌سازی _get_translation_frontend_modules_name() برای مدل ir.http به‌صراحت ثبت شود.

می‌تواند شبیه این باشد:

from odoo import models

class IrHttp(models.AbstractModel):
    _inherit = ['ir.http']

    @classmethod
    def _get_translation_frontend_modules_name(cls):
        modules = super()._get_translation_frontend_modules_name()
        return modules + ['your_module']

زمینه

برای ترجمه، تابع ترجمه نیاز دارد زبان و نام ماژول را بداند. هنگام استفاده از Environment._ زبان شناخته‌شده است و می‌توانید نام ماژول را به‌عنوان پارامتر منتقل کنید، در غیر این صورت از فراخواننده استخراج می‌شود.

در مورد odoo.tools.translate._، زبان و ماژول از context استخراج می‌شوند. برای این کار، متغیرهای محلی فراخواننده را بررسی می‌کنیم. عیب این روش این است که مستعد خطا است: ما سعی می‌کنیم متغیر context یا self.env را پیدا کنیم، اما این‌ها ممکن است وجود نداشته باشند اگر از ترجمه‌ها خارج از متدهای مدل استفاده کنید؛ یعنی داخل توابع معمولی یا comprehensionهای python کار نمی‌کند.

ترجمه‌های lazy در زمان ایجاد به ماژول متصل می‌شوند و زبان هنگام ارزیابی با استفاده از str() حل می‌شود. توجه کنید که می‌توانید یک ترجمهٔ lazy را نیز به Environment._ بدهید تا بدون هیچ حل زبان جادویی، آن را ترجمه کنید.

متغیرها

نکنید ممکن است استخراج کار کند اما متن را به‌درستی ترجمه نخواهد کرد:

_("Scheduled meeting with %s" % invitee.name)

انجام دهید متغیرهای پویا را به‌عنوان پارامتر جستجوی ترجمه تنظیم کنید (در صورت نبود placeholder در ترجمه، به منبع بازمی‌گردد):

_("Scheduled meeting with %s", invitee.name)

بلوک‌ها

نکنید ترجمهٔ خود را در چندین بلوک یا چند خط تقسیم نکنید:

# bad, trailing spaces, blocks out of context
_("You have ") + len(invoices) + _(" invoices waiting")
_t("You have ") + invoices.length + _t(" invoices waiting");

# bad, multiple small translations
_("Reference of the document that generated ") + \
_("this sales order request.")

انجام دهید در یک بلوک نگه دارید و context کامل را به مترجمان بدهید:

# good, allow to change position of the number in the translation
_("You have %s invoices waiting") % len(invoices)
_.str.sprintf(_t("You have %s invoices waiting"), invoices.length);

# good, full sentence is understandable
_("Reference of the document that generated " + \
  "this sales order request.")

جمع

نکنید اصطلاحات را به سبک انگلیسی جمع نبندید:

msg = _("You have %(count)s invoice", count=invoice_count)
if invoice_count > 1:
  msg += _("s")

انجام دهید به یاد داشته باشید که هر زبان فرم‌های جمع متفاوتی دارد:

if invoice_count > 1:
  msg = _("You have %(count)s invoices", count=invoice_count)
else:
  msg = _("You have one invoice")

زمان خواندن در برابر زمان اجرا

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

ERROR_MESSAGE = {
  # bad, evaluated at server launch with no user language
  'access_error': _('Access Error'),
  'missing_error': _('Missing Record'),
}

class Record(models.Model):

  def _raise_error(self, code):
    raise UserError(ERROR_MESSAGE[code])

نکنید جستجوی ترجمه را در زمان خواندن فایل javascript فراخوانی نکنید:

# bad, js _t is evaluated too early
var core = require('web.core');
var _t = core._t;
var map_title = {
    access_error: _t('Access Error'),
    missing_error: _t('Missing Record'),
};

انجام دهید از روش جستجوی ترجمهٔ lazy استفاده کنید:

ERROR_MESSAGE = {
  'access_error': _lt('Access Error'),
  'missing_error': _lt('Missing Record'),
}

class Record(models.Model):

  def _raise_error(self, code):
    # translation lookup executed at error rendering
    raise UserError(ERROR_MESSAGE[code])

یا انجام دهید محتوای قابل ترجمه را به‌صورت پویا ارزیابی کنید:

# good, evaluated at run time
def _get_error_message(self):
  return {
    access_error: _('Access Error'),
    missing_error: _('Missing Record'),
  }

انجام دهید در حالتی که جستجوی ترجمه هنگام خواندن فایل JS انجام می‌شود، از _lt به‌جای _t استفاده کنید تا اصطلاح هنگام استفاده ترجمه شود:

# good, js _lt is evaluated lazily
var core = require('web.core');
var _lt = core._lt;
var map_title = {
    access_error: _lt('Access Error'),
    missing_error: _lt('Missing Record'),
};