نسخه انگلیسی یک تحلیل‌گر Cache-Control که به‌تازگی عرضه شده بود، متن ژاپنی نشان می‌داد؛ وضعیت آن به‌جای "Fresh" عبارت "新鮮" را نمایش می‌داد. ریشه این اشتباه به منطق مشترکی بازمی‌گشت که رشته‌های ژاپنیِ هاردکد شده (hard-coded) را برمی‌گرداند، در حالی که صفحه فقط برچسب‌های انگلیسی را ارائه می‌کرد.

این توسعه‌دهنده مجموعه‌ای از ابزارهای سبک مرورگر را می‌سازد که هر کدام دارای یک صفحه انگلیسی و یک صفحه ژاپنی هستند و از توابع تجزیه (parsing) و منطق اصلی یکسانی استفاده می‌کنند. تنها چیزی که باید متفاوت باشد، کلماتِ نمایشی است. زمانی که تحلیل‌گر Cache-Control عرضه شد، رابط کاربری انگلیسی برچسب‌های صحیح را نمایش می‌داد، اما مقادیری که رندر می‌شدند از لایه منطق (logic layer) می‌آمدند که همچنان حاوی رشته‌های مستقیم (literals) ژاپنی بود. هیچ خطایی در کنسول ظاهر نشد؛ صفحه عادی به نظر می‌رسید، اما اطلاعات ارائه‌شده به کاربران انگلیسی‌زبان اشتباه بود.

چرا منطق مشترک می‌تواند ترجمه را خراب کند

این باگ از یک انتخاب طراحی نشأت می‌گرفت: تابع اصلی که تصمیم می‌گرفت چه چیزی نمایش داده شود، رشته‌های مستقیم را به زبان ژاپنی برمی‌گرداند. لایه صفحه که مسئول متن‌های انگلیسیِ اطراف بود، هرگز فرصتی برای جایگزینی آن مقادیر پیدا نکرد. از آنجایی که منطق و رابط کاربری (UI) به‌طور کاملی از هم جدا شده بودند، مشکل در طول آزمایش‌ها نامرئی باقی ماند؛ از نظر فنی همه چیز «درست» کار می‌کرد، هرچند زبانی که کاربر با آن روبرو می‌شد اشتباه بود.

نکته منفی این است که زبان استفاده‌شده در داخل ماژول مشترک، به زبان پیش‌فرض برای هر فرانت‌اندی (front-end) که از آن استفاده می‌کند تبدیل می‌شود. اگر زبان دیگری مورد نیاز باشد، این مقدار پیش‌فرض به یک باگ پنهان تبدیل می‌شود.

راه حل: کلیدها، بسته‌ها و یک شبکه ایمنی

نویسنده معماری را برای جداسازی وظایف (separation of concerns) بازنویسی کرد:

  • بسته‌های پیام (Message packs) اکنون تمام رشته‌های قابل‌خواندن برای هر زبان را نگه می‌دارند.
  • منطق مشترک (Shared logic) فقط کلیدهای نمادین برمی‌گرداند و هرگز متن خام ارائه نمی‌دهد.
  • صفحات (Pages) بر اساس کلید، کلمه مناسب را از بسته مربوطه جستجو می‌کنند.

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

همچنین یک مرحله تحلیل استاتیک (static-analysis) ساده اضافه شد: فرآیند ساخت (build process) فایل‌های مشترک را برای یافتن کاراکترهای ژاپنی اسکن می‌کند. اگر کاراکتری پیدا شود، بلافاصله به توسعه‌دهنده هشدار داده می‌شود تا از ورود مجدد متن‌های خارجیِ هاردکد شده جلوگیری شود.

آنچه این تجربه به نویسنده آموخت

  1. ترجمه به عنوان یک مرحله بازبینی عمل می‌کند. هنگام نوشتن پیام‌های انگلیسی، نویسنده متوجه شد که برخی معادل‌های ژاپنی مبهم هستند. ترجمه کردن باعث شد عبارت‌پردازی در هر دو زبان شفاف‌تر شود.
  2. توابع مشترکی که رشته برمی‌گردانند، زبان را برای همه تثبیت می‌کنند. اگر یک تابع زبان را تعیین کند، هر مصرف‌کننده‌ای که انتظار زبان دیگری را دارد، آن اشتباه را به ارث می‌برد. این باگ یک نقص در UI نیست، بلکه یک نقص منطقی است.

توصیه‌هایی برای کسانی که ابزارهای چندزبانه را نگهداری می‌کنند

  • از توابع اصلی، کلید برگردانید، نه رشته. اجازه دهید لایه UI مسئول بومی‌سازی (localization) باشد.
  • یا رشته‌های مورد نظر را به عنوان پارامتر به تابع پاس دهید. این کار باعث می‌شود منطق برنامه نسبت به زبان بی‌طرف (agnostic) باقی بماند.
  • ماژول‌های مشترک را برای یافتن متن‌های هاردکد شده به زبان اصلی بازرسی کنید. یک جستجوی سریع برای کاراکترهای غیر ASCII می‌تواند مشکلات پنهان را آشکار کند.
  • یک بررسی در زمان ساخت (build-time) برای کاراکترهای خارجی در کدهای مشترک اضافه کنید. تشخیص زودهنگام بهتر از سردرگمی پس از انتشار است.

آنچه باید در آینده مراقب آن باشید

نتیجه‌گیری: اگر پروژه شما کدها را بین نسخه‌های مختلف زبان به اشتراک می‌گذارد، مطمئن شوید که بخش مشترک هرگز در مورد نحوه بیان کلمات تصمیم نمی‌گیرد. اجازه دهید هر صفحه کلمات خودش را ارائه دهد تا از شر خجالتِ داشتن یک صفحه انگلیسی که به‌طور تصادفی ژاپنی صحبت می‌کند، خلاص شوید.