از کد فارسی به انگلیسی و برعکس: نگاهی به درون ابزار salam translate

salam translate یک برنامه‌ی سلام را از انگلیسی به فارسی یا از فارسی به انگلیسی بازنویسی می‌کند، بی آنکه به نام‌ها، توضیح‌ها یا رشته‌های خود شما دست بزند. اینجا می‌بینید چه چیزی را عوض می‌کند، چطور تصمیم می‌گیرد و بعد از آن چه چیزی را باید بررسی کرد.

در این مقاله
  1. salam translate به چه کاری می‌آید؟
  2. نخستین ترجمه با salam translate
  3. salam translate چه چیزهایی را بازنویسی می‌کند
  4. به چه چیزهایی دست نمی‌زند
  5. گزینه‌ها و فایل‌های خروجی
  6. رفت‌وبرگشت: فارسی به انگلیسی و دوباره به فارسی
  7. درون ابزار چه می‌گذرد
  8. محدودیت‌ها و بررسی نتیجه‌ی ترجمه‌ی کد فارسی
  9. کی از آن استفاده کنیم و کی نه
  10. پرسش‌های پرتکرار درباره‌ی ترجمه‌ی کد فارسی و انگلیسی

salam translate فرمانی در ابزارهای سلام است که یک فایل کد را میان دو املای زبان تبدیل می‌کند. این فرمان برنامه‌ی سلام را از انگلیسی به فارسی (salam translate fa) یا از فارسی به انگلیسی (salam translate en) بازنویسی می‌کند: واژه‌های کلیدی، تابع ورود، گونه‌های پایه، متدها و تابع‌های داخلی و نام بسته‌ها و تابع‌های کتابخانه‌ی استاندارد، در حالی که شناسه‌ها، توضیح‌ها، رشته‌ها و قالب‌بندی خود شما را دقیقاً همان‌طور که بودند نگه می‌دارد. نتیجه فایل تازه‌ای است که به همان برنامه کامپایل می‌شود.

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

salam translate به چه کاری می‌آید؟

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

  • معلمی نمونه‌ها را به انگلیسی می‌نویسد و نسخه‌ی فارسی را به دانش‌آموزها می‌دهد، یا برعکس؛
  • دانشجویی که با فارسی یاد گرفته، پروژه‌اش را برای انتشار در گیت‌هاب به انگلیسی می‌برد؛
  • نگه‌دارنده‌ای که فارسی نمی‌خواند گزارش باگی با یک برنامه‌ی فارسی می‌گیرد و می‌خواهد کد را بخواند؛
  • نویسندگان مستندات یک نمونه را نگه می‌دارند و آن را به هر دو زبان منتشر می‌کنند.

خط فرمانش کوتاه است:

ترمینال
salam translate fa app.salam            # انگلیسی به فارسی، فایل app.fa.salam را می‌نویسد
salam translate en app.fa.salam         # فارسی به انگلیسی، فایل app.fa.en.salam را می‌نویسد
salam translate fa src/                 # یک پوشه‌ی کامل، پوشه‌ی src-fa/ را می‌نویسد
salam translate en app.salam --rewrite  # بازنویسی همان فایل

نخستین ترجمه با salam translate

از یک برنامه‌ی معمولی انگلیسی شروع کنیم: دو واردسازی، یک توضیح، یک ساختار، تابعی با نام چندکلمه‌ای، یک Vector، یک حلقه، یک مقایسه و یک فراخوانی کتابخانه‌ی استاندارد.

سلام
import str
import math

// A point on a plane
struct Point:
    pub x: f64 = 0.0
    pub y: f64 = 0.0
end

func distance from origin(p: Point): f64:
    ret math.Sqrt(p.x * p.x + p.y * p.y)
end

func main:
    mut points := Vector {} as Vector<Point>
    defer points.free()
    points.push(Point { x = 3.0, y = 4.0 })
    points.push(Point { x = 1.0, y = 1.0 })
    each p in points:
        d := distance from origin(p)
        if d >= 2.0 and p.x != p.y:
            println str.Upper("far:"), d
        else:
            println "near:", d
        end
    end
    println "points:", points.len(), true
end
خروجیFAR: 5 near: 1.4142135623730951 points: 2 true

salam translate fa points.salam پیام translated points.salam -> points.fa.salam را چاپ می‌کند و این فایل را می‌نویسد. آنچه می‌بینید دقیقاً همان است که ابزار نوشته:

سلام
// زبان: فارسی
واردسازی رشته
واردسازی ریاضی

// A point on a plane
ساختار Point:
    همگانی x: اعشار۶۴ = 0.0
    همگانی y: اعشار۶۴ = 0.0
پایان

تابع distance from origin(p: Point): اعشار۶۴:
    برگشت ریاضی.جذر(p.x * p.x + p.y * p.y)
پایان

تابع ریشه:
    ناپایا points := وکتور {} برگردان وکتور<Point>
    دیرکن points.آزادکن()
    points.بیفزا(Point { x = 3.0، y = 4.0 })
    points.بیفزا(Point { x = 1.0، y = 1.0 })
    هر p در points:
        d := distance from origin(p)
        اگر d بزرگتربرابر 2.0 و p.x نابرابر p.y:
            سرچاپ رشته.بزرگ("far:")، d
        وگرنه:
            سرچاپ "near:"، d
        پایان
    پایان
    سرچاپ "points:"، points.طول()، درست
پایان
خروجیFAR: 5 near: 1.4142135623730951 points: 2 true

خروجی یکسان است، چون برنامه یکسان است. ببینید چه چیزی جابه‌جا شده و چه چیزی نه. import str به واردسازی رشته تبدیل شده و math.Sqrt به ریاضی.جذر، یعنی نام‌های فارسی‌ای که کتابخانه‌ی استاندارد تعریف کرده است. f64 شده اعشار۶۴، Vector شده وکتور، .push و .free شده‌اند .بیفزا و .آزادکن و main شده ریشه. مقایسه‌ها به واژه‌های مقایسه‌ی فارسی بزرگتربرابر و نابرابر تبدیل شده‌اند، ویرگول‌ها به ویرگول فارسی ، و true به درست. در همین حال Point، distance from origin، points، p و d بدون تغییر مانده‌اند، توضیح هنوز انگلیسی است و رشته‌ها هم همین‌طور.

خواننده‌ی فارسی‌زبان حالا Point را به نقطه تغییر نام می‌دهد و همین‌طور بقیه را. ابزار این کار را عمداً به انسان می‌سپارد، به دلیل‌هایی که پایین‌تر می‌آید.

salam translate چه چیزهایی را بازنویسی می‌کند

این فهرست از کد منبع خود فرمان، compiler/cli/cli_translate.salam، و از اجرای آن به دست آمده است:

دستهنمونه، انگلیسی به فارسی
واژه‌های رزروشده، از جمله and، or، notfunc به تابع، each به هر، and به و
واژه‌های لفظیtrue، false، null، this به درست، نادرست، پوچ، این
تابع ورودmain به ریشه
گونه‌های پایه و داخلی در جایگاه گونهint به صحیح، Vector<str> به وکتور<رشته>
متدهای داخلی بعد از نقطه.push() به .بیفزا()، .len() به .طول()
تابع‌های داخلیlen(x) به طول(x)، spawn به نخ‌ساز
واردسازی‌ها و اعضای کتابخانه‌ی استانداردimport math به واردسازی ریاضی، math.Sqrt به ریاضی.جذر
ثابت‌های زمان کامپایلSALAM_OS_LINUX به سلام سیستم لینوکس
عملگرهای مقایسه>= به بزرگتربرابر (در فارسی)، و واژه‌ها به نماد (در انگلیسی)
جداکننده‌ها, به ، و برعکس
رشته‌های گیومه‌دار«...» به "..." هنگام رفتن به انگلیسی
واژه‌های وابسته به جایگاهstatic، link، dynamic، use (جاسازی) در جایگاه خودشان
زبان صفحهنام عنصرها و ویژگی‌ها در بلوک‌های page:
خط زبان// lang: en جایش را به // زبان: فارسی می‌دهد

دو نکته ارزش دانستن دارد. نخست، نمادهای مقایسه فقط وقتی به واژه تبدیل می‌شوند که فایل بدون خطا تجزیه شود، چون ابزار از درخت نحوی استفاده می‌کند تا بفهمد کدام < و > واقعاً مقایسه‌اند؛ کروشه‌های گونه‌ی عام مثل Vector<T> علامت‌های زاویه‌ای خود را نگه می‌دارند. در رفتن به انگلیسی، واژه‌های مقایسه همیشه به نماد تبدیل می‌شوند. دوم، نام‌های کتابخانه‌ی استاندارد درون کامپایلر ذخیره نشده‌اند. ابزار یک بار پوشه‌ی std/ را پیمایش می‌کند، برچسب‌های @en و @fa هر بسته را می‌خواند و str.Upper را از روی برچسب خود تابع به رشته.بزرگ تبدیل می‌کند. بسته‌ای که فردا به کتابخانه‌ی استاندارد اضافه شود بدون هیچ تغییری در ابزار ترجمه می‌شود.

به چه چیزهایی دست نمی‌زند

هر چیز دیگری بایت به بایت کپی می‌شود:

  • شناسه‌های شما. نام متغیرها، تابع‌ها، ساختارها، فیلدها و جداشمارها همان‌طور که نوشته‌اید می‌مانند، به هر خطی که نوشته باشید.
  • توضیح‌ها و محتوای رشته‌ها. ابزار کد را ترجمه می‌کند، نه نثر را.
  • عددها، همراه با رقم‌هایشان. فایل فارسی‌ای که ۱۲۰ دارد بعد از ترجمه به انگلیسی هنوز ۱۲۰ دارد، که در فایل انگلیسی هم معتبر است.
  • فاصله‌ها و چیدمان. تورفتگی، سطرهای خالی و شکست سطرها همان نسخه‌ی اصلی‌اند، چون ابزار متن میان توکن‌ها را کپی می‌کند و دوباره قالب‌بندی نمی‌کند.

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

گزینه‌ها و فایل‌های خروجی

از salam help:

گزینهاثر
en یا faزبان مقصد، اجباری
--lang=CODEزبان مبدأیی که وقتی تشخیص قطعی نیست فرض می‌شود؛ به‌طور پیش‌فرض زبان هر فایل از خط زبانش یا از اینکه با کدام مجموعه واژه‌ی کلیدی بهتر جور است تشخیص داده می‌شود
--rewriteبه‌جای نوشتن فایل تازه، خود فایل‌های ورودی را بازنویسی می‌کند
--output=PATHفایل خروجی برای یک ورودی تنها، یا پوشه‌ی خروجی

بدون --output، یک فایل تنها مثل app.salam کنار خودش به شکل app.fa.salam (یا app.en.salam) نوشته می‌شود و پوشه‌ی src به src-fa کپی می‌شود. در یک پوشه، هر فایل .salam ترجمه و هر فایل دیگری کپی می‌شود، پس دارایی‌ها و salam.toml هم همراه می‌آیند. فایل‌ها و پوشه‌های پنهان نادیده گرفته می‌شوند. فایلی که از پیش به زبان مقصد است بدون تغییر کپی می‌شود و در سطر جمع‌بندی همین‌طور شمرده می‌شود، برای نمونه translate: 1 translated, 0 unchanged, 0 errors.

ابزار به زبان کد منبع حرف می‌زند: ترجمه‌ی یک فایل فارسی پیام ترجمه شد ... -> ... و یک سطر جمع‌بندی فارسی چاپ می‌کند. فقط یک نوع ورودی را رد می‌کند، فایلی با خطای واژگانی (رشته‌ی بسته‌نشده، نویسه‌ی سرگردان)، و این را هم صریح می‌گوید: cannot translate 'x.salam': fix the lexical errors first. خطاهای نحوی و گونه‌ای جلویش را نمی‌گیرند: ابزار هرگز بررسی‌کننده‌ی گونه را اجرا نمی‌کند، پس فایل نیمه‌کاره هم ترجمه می‌شود.

رفت‌وبرگشت: فارسی به انگلیسی و دوباره به فارسی

فایل فارسی را با salam translate en points.fa.salam برگردانید و مقایسه کنید:

ترمینال
salam translate fa points.salam
salam translate en points.fa.salam
diff points.salam points.fa.en.salam

تنها تفاوت سطر اول است، // lang: en، چون ابزار همیشه یک خط زبان در خروجی‌اش می‌نویسد و نسخه‌ی اصلی آن را نداشت. بقیه، از جمله توضیح، رشته‌ها، فاصله‌گذاری و ترتیب کد، یکسان است. مجموعه آزمون پروژه به همین ویژگی به شکل دیگری تکیه می‌کند: هر برنامه در tests/en/translate/ و tests/fa/translate/ به زبان دیگر ترجمه، اجرا و خروجی‌اش با خروجی مورد انتظار مقایسه می‌شود.

درون ابزار چه می‌گذرد

این فرمان حدود ۱٬۵۰۰ سطر کد سلام در compiler/cli/cli_translate.salam است و به‌جای تطبیق متن، پیشانه‌ی خود کامپایلر را دوباره به کار می‌برد:

  1. تشخیص. همان تابعی که کامپایلر به کار می‌برد تعیین می‌کند فایل با کدام بسته‌ی زبانی نوشته شده است.
  2. واژه‌کاوی. فایل با همان بسته به توکن‌ها شکسته می‌شود. هر توکن جای شروع و پایانش در متن منبع را به خاطر می‌سپارد.
  3. نمایه‌سازی. ابزار نام‌هایی را که خود فایل تعریف می‌کند جمع می‌کند (تا متغیر محلی‌ای به نام len با تابع داخلی اشتباه گرفته نشود)، به‌علاوه‌ی بسته‌هایی که وارد می‌کند و نام‌های مستعارشان، و متغیرهایی که گونه‌شان از یک بسته می‌آید.
  4. پیمایش. برای هر توکن، متن اصلی میان توکن قبلی و این توکن را کپی می‌کند و بعد یا خود توکن را بی‌تغییر می‌نویسد یا املای آن را در بسته‌ی مقصد. واژه‌های کلیدی بر اساس نوع توکن نگاشت می‌شوند؛ برای همین until و to هر دو در فارسی تا می‌شوند و درست برمی‌گردند، چون دو نوع توکن متفاوت‌اند.
  5. علامت‌گذاری. هر خط زبان قدیمی را از سرآیند برمی‌دارد و خط تازه را می‌نویسد.

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

محدودیت‌ها و بررسی نتیجه‌ی ترجمه‌ی کد فارسی

متن راهنمای خود ابزار درباره‌ی یک دام صریح است: متدی که خودتان نوشته‌اید و هم‌نام یکی از متدهای داخلی ظرف‌هاست، مثلاً .get() خودتان، در محل فراخوانی آن هم تغییر نام می‌دهد. در عمل کامپایلر املای فارسی را برای آن فراخوانی هم می‌پذیرد، پس برنامه همچنان اجرا می‌شود، اما پیش از استفاده از --rewrite خروجی را بخوانید.

محدودیت دیگر پوشش است. جدول متدهای داخلی در مترجم از جدول کامپایلر عقب است: lower، upper و bytes در نسخه‌ی کنونی در آن نیستند. این برنامه‌ی انگلیسی است:

سلام
func main:
    word := "Salam"
    println word.upper(), word.lower(), word.len()
end
خروجیSALAM salam 5

بعد از salam translate fa، .len() به .طول() تبدیل شده، اما .upper() و .lower() هنوز انگلیسی‌اند و کامپایلر همین را همراه با اصلاح می‌گوید:

متن
خطا[خطا۰۰۱]: شناسه‌ی 'upper' در یک فایل فارسی باید فارسی باشد
 --> shout.fa.salam:4:11
  |
4 |     سرچاپ word.upper()، word.lower()، word.طول()
  |           ^
  |
  = راهنما: نام فارسی آن 'بزرگ' است
  = اصلاح: نوشتن 'بزرگ'

salam check --fix shout.fa.salam هر دو اصلاح را اعمال می‌کند و بعد فایل اجرا می‌شود:

سلام
تابع ریشه:
    word := "Salam"
    سرچاپ word.بزرگ()، word.کوچک()، word.طول()
پایان
خروجیSALAM salam 5

پس روند قابل اعتماد سه فرمان است: salam translate، بعد salam check --fix روی خروجی، و بعد salam check برای اطمینان از اینکه چیزی باقی نمانده. چون فایل فارسی باید برای هر چیزی که زبان و کتابخانه‌ی استاندارد تعریف می‌کنند نام فارسی به کار ببرد و فایل انگلیسی نام انگلیسی، کامپایلر هر نامی را که از دست مترجم در رفته پیدا می‌کند. ترجمه نمی‌تواند بی‌صدا برنامه‌ی دیگری بسازد.

کی از آن استفاده کنیم و کی نه

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

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

برای مطالعه‌ی بیشتر: یک زبان برای فارسی و عربی چطور طراحی می‌شود و معرفی زبان سلام.

پرسش‌های پرتکرار درباره‌ی ترجمه‌ی کد فارسی و انگلیسی

آیا salam translate نام متغیرها و توضیح‌ها را هم ترجمه می‌کند؟

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

آیا می‌شود یک پروژه‌ی کامل سلام را از فارسی به انگلیسی تبدیل کرد؟

بله. salam translate en src/ یک نسخه‌ی ترجمه‌شده از پوشه را در src-en/ می‌نویسد، هر فایل .salam را ترجمه و بقیه را کپی می‌کند. با --rewrite فایل‌ها در جای خودشان تغییر می‌کنند؛ بهتر است این کار را روی یک نسخه‌ی تمیز در کنترل نسخه انجام دهید تا بتوانید تفاوت‌ها را بازبینی کنید.

آیا برنامه‌ی ترجمه‌شده دقیقاً همان برنامه است؟

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

آیا salam translate برای زبان‌هایی جز فارسی هم کار می‌کند؟

امروز نه. کامپایلر دو بسته‌ی زبانی دارد، انگلیسی (en) و فارسی (fa)، و این ابزار میان همین دو تبدیل می‌کند.