سهیل نادری برای کتابخانه عمومی ارومیه یک جستوجوی عضو نوشت، و شش هفته پشت سر هم شکایت گرفت که اسم فلانی در سامانه نیست. همه اسمها در پایگاه داده بودند. رکورد مهدیه رضایی را کارمندی وارد کرده بود که صفحهکلیدش یای عربی مینوشت، و خود مهدیه با یای فارسی جستوجو میکرد.
یای عربی و یای فارسی یک شکل دارند و دو نقطه کد متفاوتاند: یکی U+064A است و دیگری U+06CC. هیچ مقایسه رشتهای آن دو را برابر نمیداند و هیچ ایندکسی به داد شما نمیرسد. کاف هم همین وضع را دارد، U+0643 در برابر U+06A9. این نحو نیست؛ این چیزی است که یک زبان و کتابخانههایش درباره متن فارسی از قبل میدانند یا نمیدانند.
آنچه یک زبان واقعاً از شما میخواهد یاد بگیرید
آموزش زبان حلقه و تابع و املای فهرست را پوشش میدهد. آن نیمه کوچک است و واقعی است. زیرش مدیر بسته نشسته، فایل قفل، مرحله بیلد، اجراگر تست و راه منتشر کردن، و هیچکدام در آموزش نیستند چون هیچکدام زبان نیستند. برای یک پروژه فارسی یک لایه دیگر هم هست که در هیچ آموزش انگلیسیزبانی نمیآید: اینکه همین ابزارها با متن راستبهچپ، با رقم فارسی و با نیمفاصله چه میکنند.
لایههای زیرین در همه زبانها یک شکل دارند و به همین دلیل زبان دوم کسری از زبان اول خرج برمیدارد؛ همین است که آنچه این انتخاب واقعاً به آن بستگی دارد، و آنچه ندارد را از یک پرسش سرنوشتساز به یک تصمیم معمولی تبدیل میکند. آنچه کسری نمیشود همان لایه فارسی است، چون هر زبان جواب خودش را دارد و بعضیها اصلاً جوابی ندارند. جای این تفکیک در برنامهنویسی، از اولین متغیر تا سیستمی که بالا آمده است همین صفحه است.
چهار جملهای که سامانه کتابخانه ارومیه را درست کرد
اولین چیزی که تقریباً همه امتحان میکنند یکسانسازی یونیکد است و کار نمیکند: NFC و NFKC یای عربی را به یای فارسی تبدیل نمیکنند، چون این دو حرفهای متفاوتاند نه شکلهای متفاوت یک حرف. جدول جایگزینی را باید خودتان بنویسید، یک بار، و هم روی چیزی که ذخیره میشود اجرا کنید و هم روی چیزی که کاربر تایپ کرده است.
FIX = str.maketrans('\u064a\u0643', '\u06cc\u06a9')def norm(s): return s.translate(FIX).replace('\u200c', ' ')
دومی روی کاغذ ظاهر شد. فهرست چاپی اعضا قرار بود به ترتیب حروف الفبا باشد و نبود، چون مرتبسازی پیشفرض رشتهها بر اساس نقطه کد است، نه الفبای فارسی. سه اسم برای دیدنش کافی است: پ در جدول یونیکد بعد از ت آمده و در الفبا پیش از آن است، پس پرویز به انتهای فهرست میرود.
sorted(['پرویز', 'تهمینه', 'بهرام'])['بهرام', 'تهمینه', 'پرویز']
سومی جایی است که تفاوت زبانها به یک جمله میرسد. Intl.Collator در خود جاوااسکریپت است و پایتون معادلش را در کتابخانه استاندارد ندارد: در تنها زبانی که یک مرورگر اجرا میکند نوشتن new Intl.Collator('fa').compare کافی است و چیزی نصب نمیشود، و در خوانا، همهجا هست، و معمولاً اولین زبان باید PyICU را بیاورید، که هم یک بسته است و هم یک کتابخانه سیستمی زیرش دارد. locale.strxfrm جایگزین نیست؛ به داده زبانی سیستمعامل تکیه میکند و روی دو ماشین دو جواب میدهد.
همین تفاوت را روی یک برنامه بیاهمیت هم میشود شمرد. سروری که فقط یک خط متن برمیگرداند، با شمردن هر چیزی که واقعاً نصب میشود نه هر چیزی که پروژه اعلام کرده: جاوااسکریپت ۴۹ بسته، Rust ۴، پایتون ۲، و زبان کوچک، بیلد سریع، ساختهشده برای سرور ۰، چون خود سرور در کتابخانه استاندارد است. عدد بزرگ ضعف نیست: اکوسیستمی که همهچیز را میرساند از یک کمیته تندتر حرکت میکند و قیمتش را در درخت وابستگی میدهد.
چهارمی جایی ظاهر شد که هیچکس دنبالش نبود. نیمفاصله یک نویسه است و در طول رشته شمرده میشود، پس میآید شش نویسه است نه پنج، و ستون نام خانوادگی که بیست نویسه ظرفیت داشت اسمهای ترکیبی را بیصدا کوتاه میکرد. محدودیت طول را روی نویسه نگذارید، یا نیمفاصله را پیش از شمردن بردارید و نسخه اصلی را ذخیره کنید.
ترتیبی که تقریباً همه از وسطش رد میشوند
داخل یک زبان یک ترتیب هست و قدم میانیاش همیشه جا میماند: نحو، بعد فریمورک، و مدیر بسته و فایل قفل وسط این دو میافتند و رد میشوند، چون تنها قدمیاند که هیچچیز روی صفحه نشان نمیدهند. کسی که این ترتیب را برعکس بگیرد یک برنامه لاراول کامل میسازد و نمیتواند همان را روی ماشین دیگری بالا بیاورد، و معنی فایل قفل را دقیقاً همان روزی میفهمد که قرار بوده تحویل بدهد.
میان دوازده خوشه زیر این صفحه هم ترتیبی هست، و زبان دوم را برای کار کردن برندارید، برای دیدن مرز بردارید: زبانی از خانواده دیگر نشان میدهد کدام عادتهای شما مال زبان اول بود و کدام مال خود برنامهنویسی. سهیل بعد از پایتون یک هفته Go نوشت و تنها چیزی که از آن هفته ماند این بود که چهار جمله بالا در Go هم درستاند و فقط جای دیگری نوشته میشوند.
جایی که کار میایستد: فاصله میان نام زبان و نام آگهی
ایست این موضوع در ایران انتخاب زبان نیست. آگهی فارسی برنامهنویس لاراول میخواهد، نه برنامهنویس PHP؛ برنامهنویس جنگو میخواهد، نه برنامهنویس پایتون. کسی که شش ماه روی چیزی که بیشتر وب هنوز با آن نوشته شده وقت گذاشته و هنوز به فریمورک نرسیده، زبان را بلد است و هیچ آگهیای به او نمیخورد.
نتیجهای که از این وضع گرفته میشود تقریباً همیشه غلط است: اینکه زبان اشتباه انتخاب شده. زبان بعدی همان فاصله را از نو میسازد و شش ماه دیگر میگیرد. چیزی که فاصله را میبندد یک فریمورک از همان زبان است و یک چیز کوچک که تا آخر برود، حتی اگر همان جستوجوی عضو کتابخانه باشد.
سه چیزی که در ۲۰۲۶ برای متن فارسی باز میکنید
Intl.Collator در خود جاوااسکریپت است و ترتیب الفبایی فارسی را میدهد. جای نگه داشتن یک جدول دستی ترتیب حروف در هر پروژه را گرفت، که کاری بود که تیمهای فارسی سالها میکردند و هر کدام کمی متفاوت. رایگان است و چیزی نصب نمیشود؛ از Node 13 در سال ۲۰۱۹ جدولهای کامل زبانی همراه خود Node میآیند، پس روی سرور هم همان جواب مرورگر را میدهد.
PyICU همان کار را برای پایتون میکند و متنباز و رایگان است، ولی صادقانه باید گفت که نصبش همان قدمی است که از ایران میایستد: هم بسته را از مخزن میخواهد و هم کتابخانه ICU سیستم را. جای locale.strxfrm را گرفت، که همان کار را به داده زبانی سیستمعامل واگذار میکند و روی سروری که فقط انگلیسی نصب دارد بیصدا ترتیب اشتباه میدهد.
unicodedata در کتابخانه استاندارد پایتون است، به شبکه نیاز ندارد، و جای جدولهای دستی نام و رده حرفها را گرفت که هر پروژه فارسی برای خودش نگه میداشت. کاری که نمیکند همان است که بالا آمد، و دانستنش ده ساعت صرفهجویی است: یکسانسازی، حرفهای شبیه هم را یکی نمیکند.
پرسش بعدی این نیست که کدام زبان. این است که همین چهار جمله وقتی جستوجو به جای ترمینال داخل مرورگر اجرا شود چه شکلی میشوند، جایی که فرم و فونت و آدرس هم فارسیاند و هر چه میان یک مرورگر و یک سرور میگذرد جوابش را دارد. و برای پیدا کردن دوباره همین جملهها زیر هر زبانی، ایدههایی که با عوض شدن زبان از بین نمیروند جای بعدی است.