মূল কন্টেন্টে যান
Avence Labs
ব্লগে ফিরুন
TypeScript

মিসিং অনুবাদকে রানটাইম সারপ্রাইজ নয়, কম্পাইল এরর বানানো

প্রকাশিত 27 এপ্রিল, 20265 মিনিট পড়া

প্রতিটা মাল্টি-ল্যাঙ্গুয়েজ সাইট একসময় এমন একটা পেজ শিপ করে ফেলে যেখানে অনুবাদ থাকার কথা ছিল সেখানে একটা খালি জায়গা থাকে। বেশিরভাগ সময় এটা অনুবাদের ভুল নয় — এটা এমন একটা কী যেটা প্রাইমারি ভাষার ফাইলে যোগ হয়েছিল কিন্তু বাকি তিনটাতে কখনো কপি হয়নি, আর টুলচেইনের কেউ খেয়াল করেনি যতক্ষণ না একজন ইউজার করল।

সাধারণ সমাধান, এবং আমরা কেন সেগুলো এড়িয়ে গেলাম

সাধারণ সমাধানগুলো হলো একটা linter স্ক্রিপ্ট যেটা JSON ফাইলগুলোর মধ্যে কী-সেট diff করে (CI-তে চলে, এক কমিট দেরিতে drift ধরে), অথবা একটা রানটাইম ফলব্যাক যেটা অনুবাদ মিসিং থাকলে চুপচাপ ইংরেজি স্ট্রিং দেখায় (বাগটা ঠিক করার বদলে লুকিয়ে ফেলে, আর "সাময়িকভাবে" মিসিং একটা স্ট্রিং মিসিংই থেকে যাওয়ার একটা প্রবণতা থাকে)। দুটোই ডিকশনারির গঠনকে চেক করার মতো ডেটা হিসেবে দেখে, একটা এনফোর্স করার মতো টাইপ হিসেবে নয়।

আমরা যা করেছি

ডিকশনারিগুলো সাধারণ TypeScript অবজেক্ট, প্রতি ভাষায় একটা করে, আর ইংরেজি ফাইলটা ক্যানোনিকাল:

const en = {
  hero: { headline: "...", body: "..." },
  nav: { items: ["Home", "About", "Contact"] },
} as const;

বাকি প্রতিটা locale লেখা হয় satisfies Dictionary দিয়ে, যেখানে Dictionary টাইপটা ইংরেজি অবজেক্ট থেকেই একটা রিকার্সিভ mapped type দিয়ে তৈরি:

type Widen<T> = T extends string ? string
  : T extends number ? number
  : T extends object ? { readonly [K in keyof T]: Widen<T[K]> }
  : T;

export type Dictionary = Widen<typeof en>;

Widen ইংরেজি ডিকশনারির গঠনের মধ্য দিয়ে হেঁটে প্রতিটা লিটারেল স্ট্রিংকে সাধারণ string টাইপে বদলে দেয় — তাই একটা অনুবাদ সম্পূর্ণ ভিন্ন শব্দ ব্যবহার করতে পারে, কিন্তু যে অবজেক্টে সেটা থাকে তার গঠন ইংরেজির সাথে হুবহু মিলিয়ে লক করা থাকে, কী-বাই-কী, নেস্টিং লেভেল-বাই-লেভেল।

Tuple-এর ক্ষেত্রটা কেন গুরুত্বপূর্ণ

Mapped type অবজেক্টের মতো array-এর উপরেও একইভাবে চলে, মানে এটা শুধু কী-এর নাম নয়, দৈর্ঘ্যও সংরক্ষণ করে। উপরের nav.items ইংরেজি ফাইলে স্ট্রিং-এর একটা ৩-tuple, তাই Dictionary["nav"]["items"]-ও একটা ৩-tuple — string[] নয়। অনুবাদিত একটা ফাইলে দুটো বা চারটা আইটেম থাকলে, TypeScript ঠিক সেই কল সাইটে Source has 2 element(s) but target requires 3 রিপোর্ট করে — next build-এর সময়, কিছু শিপ হওয়ার আগেই। আমরা এটার উপর নিয়মিত নির্ভর করি: নেভিগেশন মেনু, ফিচার চেকলিস্ট, স্ট্যাট রো — সবই নির্দিষ্ট-দৈর্ঘ্যের tuple হিসেবে লেখা, বিশেষভাবে যাতে একজন অনুবাদক দুর্ঘটনাক্রমে একটা এন্ট্রি বাদ দিতে বা ডুপ্লিকেট করতে না পারেন বিল্ড সেটা ধরে না ফেলে।

যে বিনিময়টা মেনে নিয়েছি

এটা কম্পাইল-টাইম নিশ্চয়তা কিনে নেয়, বিনিময়ে প্রতিটা locale ফাইলকে হতে হয় একটা পূর্ণাঙ্গ, ভালোভাবে টাইপ করা TypeScript মডিউল — একজন non-engineer সহজে এডিট করতে পারবে এমন একটা সহজ JSON ফাইল নয়। চার-ভাষার একটা সাইটের জন্য, যেটা একই টিম মেইনটেইন করে যারা কোডবেসের মালিক, এই বিনিময়টা মূল্যবান ছিল। বাইরের অনুবাদকরা সরাসরি স্ট্রিং এডিট করেন এমন অনেক বড় একটা লোকালাইজেশন অপারেশনের জন্য, একটা schema-validated JSON পাইপলাইন সম্ভবত ভালো পছন্দ হতো — মূল কথাটা এই নয় যে এই নির্দিষ্ট কৌশলটা সবসময় সঠিক, বরং এই যে ডিকশনারির গঠনটা ঠিক সেই ধরনের একটা invariant যা একটা টাইপ সিস্টেম ধরে রাখতে ভালো, আর প্রশ্ন করা মূল্যবান যে আপনারটা এখন সেটা করছে কি না।