دو رشتهٔ «یکسان» میتوانند در پایگاه داده برابر نباشند: «ی» فارسی با U+06CC و «ی» عربی با U+064A فقط شبیه هم دیده میشوند. یک زمان ثابت UTC میتواند در تهران روز دیگری باشد؛ مبلغ «۱۰۰٬۰۰۰ تومان» نیز با «۱۰۰٬۰۰۰ ریال» ده برابر تفاوت دارد. اگر تست فقط تغییر زبان منو و چند Screenshot را ببیند، این خطاهای واقعی از آن عبور میکنند.
تست i18n و L10n باید یک قرارداد کامل را از ورودی تا ذخیره، پردازش، نمایش و خروجیهای جانبی بررسی کند. در این راهنما، ابتدا تفاوت جهانیسازی و بومیسازی را روشن میکنیم، سپس قرارداد Locale و ماتریس ریسک میسازیم و آن را برای فارسی ایران، RTL، Unicode، تاریخ شمسی، منطقهٔ زمانی تهران، ریال/تومان و اتوماسیون Playwright به سناریوهای قابلاجرا تبدیل میکنیم.
i18n و L10n دقیقاً چه هستند؟
Internationalization یا i18n طراحی و پیادهسازی نرمافزار بهگونهای است که بدون بازنویسی منطق اصلی بتواند زبان، خط، منطقه، تقویم، منطقهٔ زمانی و قواعد قالببندی متفاوت را پشتیبانی کند. عدد ۱۸ تعداد حروف میان I و n در واژهٔ Internationalization است.
Localization یا L10n تطبیق یک محصول i18nشده برای بازار یا Locale مشخص است: ترجمه، واژهنامه، قالب تاریخ و عدد، تصاویر، قوانین محصول، محتوای حقوقی و کیفیت تجربهٔ همان مخاطب. Globalization یا g11n گاهی نام چتر فرایند کسبوکاری و فنی عرضه در چند بازار است.
| حوزه | پرسش اصلی تست | نمونهٔ نقص |
|---|---|---|
| i18n | آیا معماری از تفاوتها بدون Fork کد پشتیبانی میکند؟ | متن هاردکد، چیدمان فقط LTR، Parser وابسته به فرمت انگلیسی |
| L10n | آیا خروجی برای یک Locale خاص درست و طبیعی است؟ | ترجمهٔ نادرست، «تومان» با مقدار ریالی، تاریخ شمسی اشتباه |
| بازار/محصول | آیا سیاست تجاری و حقوقی مقصد درست اعمال شده است؟ | روش پرداخت ناموجود، متن رضایت یا نشانی نامتناسب |
تست i18n موفق ثابت نمیکند ترجمهٔ فارسی خوب است؛ LQA خوب هم ثابت نمیکند Parser در Locale دیگری امن است. هر کدام مرز اثبات خودش را دارد.
زبان، Locale، منطقه و جهت را یکی نگیرید
fa زبان فارسی را معرفی میکند و fa-IR فارسی در زمینهٔ منطقهٔ ایران را. با این حال، Language Tag بهتنهایی واحد پول، منطقهٔ زمانی یا تقویم انتخابی کاربر را قطعی نمیکند. ممکن است کاربری زبان فارسی، تقویم میلادی، ارقام لاتین و منطقهٔ زمانی اروپا را انتخاب کرده باشد.
تگهای زبان بر اساس BCP ۴۷ ساخته میشوند. RFC ۵۶۴۶ از IETF ساختار زیرتگهای زبان، Script، Region، Variant و Extension را تعریف میکند. پس مقدارهایی مانند fa_IR و Persian-Iran را بهعنوان Language Tag اختراع نکنید؛ قالب استاندارد وب fa-IR است.
- Language: فارسی، انگلیسی یا عربی؛
- Script: خط عربی یا لاتین و جهت غالب آن؛
- Region: بازار و قراردادهای منطقهای؛
- Calendar: هجری شمسی، میلادی یا انتخاب دیگر؛
- Time zone: مانند
Asia/Tehran؛ - Numbering system: ارقام فارسی/Extended Arabic-Indic یا لاتین؛
- Currency/domain unit: IRR، ریال یا نمایش محصولی تومان؛
- Direction: RTL/LTR؛ ویژگی ارائه است و از نام زبان بهتنهایی استنتاج کامل نمیشود.
پیش از تست، قرارداد Locale بنویسید
چکلیست بدون Oracle فقط فهرست چیزهایی است که باید «نگاه شوند». قرارداد Locale منبع قضاوت را برای هر تبدیل مشخص میکند:
| بخش قرارداد | نمونه برای محصول ایرانی | شاهد تست |
|---|---|---|
| Locale پشتیبانیشده | fa-IR و en-US |
Registry نسخهدار و تنظیم حساب |
| ترتیب انتخاب | انتخاب کاربر ← حساب/سازمان ← مرورگر ← پیشفرض امن | تست هر منبع و تعارض آنها |
| Fallback ترجمه | fa-IR → fa → en یا خطمشی صریح دیگر |
کلید گمشده و Telemetry fallback |
| مدل داخلی پول | عدد صحیح ریال + کد/واحد صریح | API و Ledger، مستقل از متن UI |
| ورودی عدد | پذیرش یا رد ارقام فارسی، عربی و لاتین طبق فیلد | جدول نمونه و خطای مشخص |
| زمان و تقویم | Instant در UTC؛ نمایش با Asia/Tehran و تقویم انتخابی |
Instant ثابت، zone و calendar ثبتشده |
| Normalization | NFC برای متن عادی؛ نگاشت ی/ک فقط در حوزهٔ مصوب | ورودی کدپوینتی و خروجی/ذخیره |
| جهت | سند فارسی RTL؛ شناسهها و قطعات لاتین ایزوله | DOM معنایی + بازبینی دیداری/کیبورد |
قرارداد باید Owner و نسخه داشته باشد. ارتقای CLDR، ICU، tzdata، فونت یا فایل ترجمه میتواند خروجی را بدون تغییر مستقیم کد کسبوکار عوض کند؛ این هویتها را در گزارش اجرا ثبت کنید.
ماتریس تست Locale را بر پایهٔ ریسک بسازید
ضرب همهٔ زبانها در همهٔ مرورگرها، دستگاهها، مناطق زمانی و کانالها به انفجار ترکیبی میرسد. ابتدا جریانهای پرپیامد را با ماتریس تست مبتنی بر ریسک اولویتبندی کنید، سپس از ترکیب نماینده و Pairwise برای بقیه استفاده کنید.
ابعاد ماتریس
- Language Tag، Script و جهت پایه؛
- Region، ارز، واحد دامنه و قواعد محصول؛
- تقویم، منطقهٔ زمانی و Instant مرزی؛
- سیستم ارقام، صفحهکلید و روش ورودی؛
- مرورگر، سیستمعامل، اندازهٔ صفحه و Font fallback؛
- کانال: Web، Mobile، API، Email، SMS، Push، PDF و CSV؛
- منبع Locale: پروفایل، سازمان، URL، Header یا پیشفرض؛
- نسخهٔ Resource bundle، CLDR/ICU، tzdata و Feature Flag.
سه لایهٔ پوشش
- Smoke هر Locale: بارگذاری Resource، نبود کلید خام، جهت و مسیر حیاتی؛
- Regression نماینده: ترکیبهای ریسکمحور در واحد، Component، API و E2E؛
- LQA انسانی: زبان، لحن، فرهنگ، دسترسپذیری و تجربه روی محصول واقعی.
برای پوشش دستگاه و مرورگر، منطق انتخاب را با راهنمای Browser و Device Matrix ترکیب کنید؛ Locale نباید ماتریس سازگاری جدا و بدون اولویت بسازد.
Unicode و UTF-۸: شرط لازم، نه اثبات نهایی
UTF-۸ از مسیر مرورگر، API، Queue، Log، پایگاه داده، جستوجو و Export باید بدون تبدیل ناخواسته عبور کند. راهنمای کوتاه i18n وب از W3C استفاده و اعلام UTF-۸، پرهیز از اتصال رشته، پشتیبانی فرمهای محلی و تعیین جهت را توصیه میکند. اما ذخیرهٔ صحیح بایتها هنوز Equality، Search و Sort درست را ثابت نمیکند.
کدپوینتهای دامدار فارسی
| نمایش | کدپوینت | نام/ریسک |
|---|---|---|
| ی | U+06CC |
Persian Yeh |
| ی | U+064A |
Arabic Yeh؛ شبیه ولی متفاوت |
| ک | U+06A9 |
Keheh فارسی |
| ک | U+0643 |
Arabic Kaf؛ شبیه ولی متفاوت |
| | U+200C |
ZWNJ یا نیمفاصله؛ نامرئی اما معنادار در واژه |
| ۰…۹ | U+06F0…U+06F9 |
Extended Arabic-Indic / ارقام رایج فارسی |
| ۰…۹ | U+0660…U+0669 |
Arabic-Indic؛ با ارقام فارسی یکسان نیست |
| 0…9 | U+0030…U+0039 |
ASCII؛ در شناسهها و قراردادهای فنی رایج |
تست Round-trip بسازید: ورود از کیبورد، API و Import؛ ذخیره؛ بازیابی؛ جستوجو؛ و Export. علاوه بر ظاهر، کدپوینتها و طول Grapheme را در شواهد تشخیصیِ امن نمایش دهید.
Normalization را متناسب با دامنه تست کنید
رشتههای canonically equivalent ممکن است بایتهای متفاوت داشته باشند. ضمیمهٔ استاندارد Unicode شمارهٔ ۱۵ فرمهای NFC، NFD، NFKC و NFKD را تعریف میکند و هشدار میدهد NFKC/NFKD تمایزهای سازگاری را حذف میکنند؛ بنابراین نباید کورکورانه روی هر متن اعمال شوند.
- برای محتوای عادی، سیاست NFC را از ورودی تا Index و خروجی تست کنید.
- نگاشت ی→ی و ک→ک بخشی از Unicode Normalization استاندارد نیست؛ یک قاعدهٔ دامنهای جداست.
- در Search نام، میتوانید متن اصلی را حفظ و یک کلید جستوجوی مصوب بسازید.
- برای Password، Token، Hash، امضای دیجیتال، IBAN یا شناسهٔ قراردادی، تبدیل خودسرانه نکنید؛ Equality باید صریحاً تعریف شود.
- Transformation را قابلمشاهده کنید تا کاربر بداند چه چیزی تغییر کرده و تیم بتواند Incident را بازتولید کند.
سناریوهای مهم شامل فاصله در برابر ZWNJ، کاراکتر ترکیبی، Emoji چندکدپوینتی، متن تهی پس از Sanitization، Copy/Paste از پیامرسان و ورودی حاوی کنترل نامرئی است.
RTL فقط راستچینکردن CSS نیست
برای سند فارسی، زبان و جهت را در HTML صریح کنید: <html lang="fa-IR" dir="rtl">. راهنمای اعلام زبان در HTML از W3C استفاده از lang روی عنصر html و علامتگذاری بخشهای با زبان دیگر را توضیح میدهد. زبان به فناوری کمکی، جستوجو و پردازش کمک میکند؛ ولی lang جای dir نیست.
چکلیست ساختار و چیدمان RTL
- از CSS Logical Properties مانند
margin-inline-startبهجای نسخههای پراکندهٔ left/right استفاده شده است؟ - آیکون جهتدار مانند بازگشت و Progress با معنا Mirror شده، اما آیکون غیرجهتدار بیدلیل برنگشته است؟
- ترتیب DOM، Focus و Screen Reader منطقی مانده و فقط با CSS معکوس نشده است؟
- جدول، Breadcrumb، نمودار، Slider و صفحهبندی در RTL قابلفهماند؟
- Tooltip، Modal، Toast و Validation message از قاب بیرون نمیزنند؟
راهنمای ساختار RTL در HTML از W3C تعیین جهت پایه با dir، استفاده از Propertyهای منطقی و dir="auto" برای ورودی کاربر را تشریح میکند.
متن دوجهته را با دادهٔ واقعی آزمایش کنید
فارسی در کنار شماره سفارش، URL، Email، کد تخفیف و شناسهٔ شبا یک خط دوجهته میسازد. الگوریتم Bidi بیشتر ترتیب را حل میکند، اما مرز قطعهٔ LTR ممکن است نیاز به ایزولهسازی معنایی با <bdi> یا dir="auto" داشته باشد.
این رشتهها را در عنوان، دکمه، جدول، اعلان، Log و Copy/Paste تست کنید:
سفارش AB-123-IR پرداخت شد؛شماره پیگیری 2026/08/11-A9؛ایمیل user+qa@example.test ثبت شد؛IR820540102680020817909002کنار برچسب فارسی؛- رشتهای که با عدد، پرانتز یا Emoji شروع و تمام میشود.
فقط Screenshot کافی نیست. Selection، حرکت Cursor، Backspace، Copy/Paste، خواندن Screen Reader و ترتیب متن در Clipboard را نیز مشاهده کنید.
ارقام فارسی، ورودی عدد و شناسهها
سیاست «همهٔ رقمها را فارسی کن» میتواند شناسهٔ فنی، کد OTP یا دادهٔ API را خراب کند. برای هر فیلد سه پرسش جدا دارید: چه چیزی پذیرفته میشود، مدل canonical چیست و چه چیزی نمایش داده میشود؟
| فیلد | ورودی محتمل | مدل و نمایش پیشنهادیِ قراردادی |
|---|---|---|
| مبلغ | فارسی/لاتین، جداکننده، Paste | Decimal/Integer canonical؛ نمایش Locale-aware با واحد صریح |
| OTP | کیبورد عددی و Paste پیامک | قرارداد پذیرش رقم روشن؛ Compare امن با canonical digits |
| شماره سفارش | حروف لاتین، خط تیره، عدد | Exact identifier؛ نمایش در Bidi isolate |
| کد ملی | ممکن است صفر آغازین داشته باشد | String، نه Number؛ طول و checksum طبق قرارداد دامنه |
| شماره موبایل | 09… یا +98… |
Parsing و canonical form صریح، بدون Regex جهانی سادهانگارانه |
سناریوهای Paste شامل فاصلهٔ عادی/نامرئی، جداکنندهٔ عربی، علامت منفی، اعشار، صفر آغازین و مخلوط سه مجموعه رقم را اضافه کنید. پیام خطا باید بگوید چه فرمتی پذیرفته است.
جستوجو، مرتبسازی و مقایسهٔ فارسی
Binary order برای فهرست نامهای فارسی الزاماً ترتیب مورد انتظار کاربر نیست. Collation باید با Locale و نیاز دامنه تعیین شود. برای Search نیز دربارهٔ ی/ی، ک/ک، اعراب، ZWNJ، فاصله، بزرگی/کوچکی حروف لاتین و typo tolerance قرارداد بنویسید.
- نتایج Search با نسخهٔ Index و Normalization یکساناند؟
- مرتبسازی Server و Client نتیجهٔ متناقض نمیدهند؟
- Pagination پس از تغییر Collation پایدار است؟
- Exact match شناسه با fuzzy match نام قاطی نشده است؟
- Highlight نتیجه مرز Grapheme و RTL را خراب نمیکند؟
دادهٔ تست را از نامهای کوتاه و لاتین فراتر ببرید. راهنمای مدیریت داده تست برای ساخت دادهٔ مصنوعی، مالکیت و TTL مفید است.
تاریخ، تقویم و منطقهٔ زمانی را جدا کنید
«تاریخ شمسی» سه تصمیم متفاوت را پنهان میکند: لحظهٔ واقعی، منطقهٔ زمانی و تقویم نمایش. برای رویداد، یک Instant بدون ابهام ذخیره کنید؛ سپس آن را با منطقهٔ زمانی IANA و تقویم انتخابی نمایش دهید. برای تاریخ کسبوکاری بدون زمان—مثلاً تاریخ تولد یا سررسید قراردادی—مدل مناسب میتواند Local Date به همراه Calendar/Rule باشد.
پایگاه دادهٔ منطقهٔ زمانی IANA تاریخچه و قواعد قابلخواندن ماشین را نگه میدارد و با تغییر تصمیمهای سیاسی بهروزرسانی میشود. پس Asia/Tehran را به offset ثابت +03:30 تبدیل نکنید؛ نسخهٔ tzdata محیط را در شاهد ثبت کنید.
سناریوهای مرزی زمان برای ایران
- لحظهٔ نزدیک نیمهشب تهران که تاریخ UTC و محلی متفاوت است؛
- پایان اسفند و قواعد سال کبیسهٔ تقویم مورد استفاده؛
- تبدیل رفتوبرگشت Gregorian ↔ Persian بدون جابهجایی روز؛
- تاریخ تاریخی در نسخههای متفاوت tzdata؛
- Session یا Token که بر Instant منقضی میشود، نه متن تاریخ نمایش؛
- گزارش «روزانه» که مرز روز را بر اساس منطقهٔ سازمان میبندد؛
- کاربر فارسی در منطقهٔ زمانی دیگری و کاربر انگلیسی در تهران.
استاندارد Unicode LDML یا UTS #۳۵ مدل دادهٔ Locale و قالبهای تاریخ، عدد، واحد و ترجیحات را مشخص میکند. خروجی کتابخانه را بهعنوان حقیقت کسبوکار فرض نکنید؛ نسخه و قرارداد محصول را هم بررسی کنید.
ریال و تومان را به Locale Library واگذار نکنید
IRR کد ارز ریال ایران است؛ «تومان» یک واحد رایج دامنهای است و تبدیل آن باید در قرارداد محصول صریح باشد. Formatter ممکن است نماد، نام یا تعداد رقم متفاوتی تولید کند، اما نمیداند کسبوکار شما چه زمانی تومان را میپذیرد.
چکلیست پول
- مقدار canonical و واحد در API، Database، Queue و Ledger صریح است؟
- تبدیل ریال/تومان دقیقاً یکبار و در مرز تعیینشده انجام میشود؟
- از Float برای مبلغ استفاده نشده و قاعدهٔ گردکردن مشخص است؟
- عدد و واحد در UI، رسید، Email، PDF و Export با هم سازگارند؟
- Copy/Paste جداکنندهها را بدون تغییر مرتبهٔ بزرگی Parse میکند؟
- حداقل/حداکثر، صفر، مبلغ منفی، Refund و جمع سبد آزموده شدهاند؟
- در Log و Error، واحد کنار مقدار وجود دارد؟
مثال پرریسک: UI «۱۲۵٬۰۰۰ تومان» نشان میدهد، API باید طبق قرارداد { "amount": 1250000, "unit": "IRR" } بفرستد و Ledger همان ۱٬۲۵۰٬۰۰۰ ریال را یکبار ثبت کند. Assertion روی متن UI بهتنهایی کافی نیست.
رشتهها را ترجمهپذیر طراحی کنید
اتصال "سلام " + name + "!" فقط یک مشکل کدنویسی نیست؛ ترتیب، جنس، جمع و نشانهگذاری در زبانها فرق میکند. هر پیام باید یک واحد معنایی با Placeholder نامدار باشد و مترجم Context، Screenshot، محدودیت و توضیح متغیرها را ببیند.
- کلید پایدار است و متن منبع بهعنوان شناسه استفاده نشده؟
- Placeholderها نامدار، typed و در ترجمه حفظ شدهاند؟
- Plural/Select به قواعد Locale سپرده شده، نه شرطهای انگلیسی؟
- HTML/Markdown داخل ترجمه محدود، Escape و از نظر XSS بررسی شده است؟
- ترجمهٔ گمشده، خالی، قدیمی و Extra key در Build قابلمشاهدهاند؟
- Fallback کنترلشده است و متن چندزبانهٔ ناخواسته را پنهان نمیکند؟
راهنمای MessageFormat در ICU الگوهای Argument، Select و Plural را توضیح میدهد. تست باید شاخههای واقعی پیام، حفظ Placeholder و خروجی خطرناک را پوشش دهد؛ صرف Compileشدن فایل ترجمه کافی نیست.
شبهبومیسازی را پیش از ترجمه اجرا کنید
Pseudo-localization رشتهها را به شکل کنترلشده تغییر میدهد تا نقص معماری زودتر دیده شود. این تکنیک جای ترجمهٔ واقعی یا بازبین بومی را نمیگیرد.
سه پروفایل مفید
- Expansion: متن را با نسبت آزمایشی مبتنی بر دادهٔ محصول بلند و در کروشه محصور کنید تا clipping و متن هاردکد دیده شود.
- Accented/Unicode: کاراکترهای غیرASCII و ترکیبی تزریق کنید، در حالی که Placeholder و Markup سالم میمانند.
- Pseudo-RTL: جهت و متن مختلط را فعال کنید تا وابستگی به left/right و ترتیب نامناسب آشکار شود.
در CI بررسی کنید همهٔ رشتههای قابلمشاهده از Resource میآیند، Placeholderها گم نشدهاند و Screenshotهای صفحات کلیدی Overflow ندارند. درصد Expansion قانون جهانی نیست؛ آن را از توزیع طول ترجمههای واقعی محصول تنظیم کنید.
تست L10n سه لایه دارد
بازبینی زبانی
دقت معنا، اصطلاحات، دستور، املا، لحن، جمع، ضمیر و سازگاری با واژهنامه بررسی میشود. مترجم باید Context داشته باشد؛ رشتهٔ «Save» بدون اینکه دکمه است یا اسم، قابلقضاوت نیست.
بازبینی کارکردی
لینک، Shortcut، Validation، پرداخت، Search، فایل، Notification و انتخاب Locale اجرا میشوند. ترجمهٔ درست اگر Placeholder را شکسته یا Route را تغییر داده باشد، محصول هنوز معیوب است.
بازبینی دیداری و فرهنگی
Clipping، Font fallback، Line height، جهت، تصویر، رنگ، مثال، واحد و محتوای حساس بازار بررسی میشوند. نظر یک فرد بومی دادهٔ کیفی مهمی است، نه تضمین جهانی؛ بازبین، تصمیم و نسخهٔ محتوا را ثبت کنید.
دسترسپذیری در Locale فارسی
ترجمهٔ قابلدیدن لزوماً قابلخواندن برای فناوری کمکی نیست. lang درست به تلفظ Screen Reader کمک میکند و dir ترتیب نمایش را هدایت میکند. راهنمای کامل تست دسترسپذیری و WCAG ۲.۲ را در کنار سناریوهای زیر اجرا کنید:
- زبان صفحه و قطعهٔ انگلیسی داخل آن درست علامتگذاری شده است؛
- ترتیب Focus با منطق کار و نه صرفاً جهت بصری هماهنگ است؛
- Error با فیلد مرتبط و به فارسی قابلفهم اعلام میشود؛
- Live region، Dialog و Toast ترجمهشده بهموقع خوانده میشوند؛
- Zoom، اندازه متن و فونت جایگزین باعث clipping نمیشود؛
- آیکون بدون متن، نام دسترسپذیر بومی و دقیق دارد؛
- کیبورد فیزیکی و On-screen فارسی، Shortcutها را از کار نمیاندازند.
فرم نام، نشانی و تلفن را محلی تست کنید
فرضهایی مانند «نام دو بخش دارد»، «همهٔ نشانیها یک خطاند» یا «شماره تلفن همیشه ده رقم است» دادهٔ کاربر را رد میکنند. فیلدها را فقط تا حد نیاز کسبوکار ساختیافته کنید.
- نام تکبخشی، چندبخشی، فارسی/لاتین، ZWNJ و فاصلهٔ اضافی؛
- استان، شهر، کدپستی با صفر آغازین و نشانی چندخطی؛
- موبایل ملی و قالب بینالمللی
+98؛ - کد ملی/شناسه با مدل String، Validation مرحلهای و پیام امن؛
- Auto-complete و Browser autofill در RTL؛
- Paste، IME، Voice input و اصلاح خودکار کیبورد موبایل؛
- حفظ متن اصلی در کنار نسخهٔ canonical برای موارد مجاز.
برای PII از دادهٔ مصنوعی معتبر استفاده کنید؛ Screenshot، Video و Trace تست نباید نام، تلفن یا شناسهٔ واقعی را منتشر کنند.
همهٔ کانالها را در قرارداد بگنجانید
| کانال | ریسک i18n/L10n | شاهد |
|---|---|---|
| Web/Mobile | RTL، Font، keyboard، deep link و cache Locale | DOM/semantics، screenshot هدفمند، device run |
| API/Webhook | تبدیل متن نمایش به مدل canonical، header و error code | Schema، payload و contract test |
| Email/SMS/Push | Bidi، Placeholder، محدودیت طول، لینک و fallback | render واقعی و capture امن |
| PDF/Print | Font embedding، glyph shaping، pagination و جدول RTL | متن استخراجشده + بازبینی تصویری |
| CSV/Excel | Encoding، delimiter، formula injection و ارقام | بازکردن در مصرفکنندهٔ هدف و round-trip |
| Search/Index | Normalization/Collation متفاوت با Database | query corpus نسخهدار |
ماتریس تست Cross Browser با Playwright به شما کمک میکند اختلاف موتورهای رندر و سیستمعامل را از نقص ترجمه یا Locale جدا کنید.
امنیت Unicode و محتوای بومیشده
کاراکترهای همشکل، کنترلهای Bidi و متن نامرئی میتوانند Log، شناسه، نام کاربری یا حتی مرور کد را گمراه کنند. استاندارد UTS #۳۹ درباره سازوکارهای امنیت Unicode روشهایی برای شناسایی Confusableها و محدودسازی شناسهها ارائه میکند. خطمشی شما باید متناسب با نوع فیلد باشد، نه ممنوعیت عمومی زبانهای غیرلاتین.
- نام نمایشی میتواند گسترده باشد؛ نام کاربری/دامنه/شناسه سیاست محدودتر و هشدار شباهت میخواهد.
- کنترلهای Bidi غیرمنتظره در Source، Log و فیلد امنیتی شناسایی و قابلمشاهده شوند.
- Placeholder ترجمه با Context مناسب Escape شود و HTML مترجم قابلاعتماد فرض نشود.
- Resource bundle و TMS دسترسی، Audit trail و امضای Release داشته باشند.
- Error بومیشده Secret، Stack، Token یا وجود حساب کاربری را افشا نکند.
- Normalization پیش از Validation و پس از آن طبق ترتیب مستند و تستشده انجام شود.
مرز انسان و اتوماسیون در تست بومیسازی
| کنترل مناسب ماشین | داوری مناسب انسان |
|---|---|
| کلید مفقود/اضافی، Placeholder، Encoding، Schema | معنای ترجمه، لحن و تناسب فرهنگی |
| Hardcoded text، API ممنوع، Property منطقی CSS | خوانایی، سلسلهمراتب بصری و طبیعیبودن UI |
| Formatter/Parser با Golden و boundary corpus | قابلفهمبودن تاریخ، پول و پیام برای کاربر هدف |
| Pseudo-localization، screenshot diff، overflow heuristic | تشخیص تغییر معنادار از اختلاف رندر بیاهمیت |
| E2E مسیر حیاتی با Locale/zone مشخص | Exploration، فناوری کمکی و رفتار فرهنگی پیچیده |
Ruleهای قطعی مانند متن هاردکد و استفاده از left/right را با الگوی تحلیل استاتیک و Quality Gate خودکار کنید. بازبین انسانی نباید وقت خود را صرف تکرار خروجی Linter کند.
نمونه تست Locale با Playwright
مستند Emulation در Playwright تنظیم Locale و Timezone را در BrowserContext نشان میدهد. این قابلیت محیط را شبیهسازی میکند، اما برای نتیجهٔ مالی یا زمان کسبوکاری هنوز به Oracle مستقل نیاز دارید.
import { test, expect } from '@playwright/test';
test.use({
locale: 'fa-IR',
timezoneId: 'Asia/Tehran',
});
test('Persian checkout keeps rial semantics and RTL contract', async ({ page, request }) => {
const order = await createOrder({ amountRial: 1_250_000 });
await page.goto(`/checkout/${order.id}`);
await expect(page.locator('html')).toHaveAttribute('lang', 'fa-IR');
await expect(page.locator('html')).toHaveAttribute('dir', 'rtl');
await expect(page.getByTestId('payable-amount'))
.toHaveText('۱٬۲۵۰٬۰۰۰ ریال');
await page.getByRole('button', { name: 'پرداخت' }).click();
await completeSandboxPayment(order.id);
const response = await request.get(`/test-support/orders/${order.id}`);
expect(await response.json()).toMatchObject({
status: 'PAID', amountRial: 1_250_000, ledgerEntries: 1
});
});
در پروژهٔ واقعی، helperها باید دادهٔ یکتا، پاکسازی امن و هویت Run داشته باشند. برای جلوگیری از Abstractionهای مبهم، اصول کد تست قابل نگهداری را اعمال کنید.
معماری پیشنهادی Suite اتوماسیون
- Static: کلید، Placeholder، Hardcode، Encoding، Bidi control و CSS physical property؛
- Unit: Parser/Formatter، fallback، Normalization، plural/select و تبدیل دامنه؛
- Property/Fuzz: Unicode corpus، round-trip، طول و رشتههای مختلط؛
- Component: pseudo locales، overflow، جهت، state و accessibility tree؛
- API/Contract: مدل canonical، Error code و نشتنکردن متن نمایش در قرارداد؛
- Visual: صفحات نماینده با OS/font/browser pinشده و Review انسانی؛
- E2E: تعداد کمی مسیر حیاتی در ترکیبهای پرریسک؛
- LQA/Exploratory: بازبین بومی و سناریوهای تازه در نسخهٔ Candidate.
Screenshot را Oracle همهچیز نکنید. اختلاف Anti-aliasing میتواند نویز بسازد و در عین حال خطای Ledger را نبیند. «قرارداد کنترل و مشاهده» در راهنمای تستپذیری نرمافزار به طراحی seamهای Locale، clock و evidence کمک میکند.
ماتریس عملی فارسی و بازار ایران
| ریسک | داده/محرک | Oracle اصلی | سطح |
|---|---|---|---|
| ی/ک عربی و فارسی | چهار ترکیب در نام و Search | سیاست Normalization + حفظ متن اصلی | Unit/API |
| ZWNJ و فاصله | «میشود»، «می شود»، «میشود» | Search contract و نمایش بدون خرابی | Unit/Search |
| سه مجموعه رقم | ۱۲۳، ۱۲۳، ۱۲۳ و مخلوط | پذیرش/رد و canonical value صریح | Unit/Component |
| Bidi شناسه | متن فارسی + AB-123 |
DOM isolate، visual و Clipboard | Component/E2E |
| ریال/تومان | ۱۲۵٬۰۰۰ تومان | ۱٬۲۵۰٬۰۰۰ IRR در API/Ledger | API/E2E |
| نیمهشب تهران | Instant دو سوی مرز روز | Local date طبق Asia/Tehran | Unit/API |
| تقویم شمسی | پایان اسفند/سال کبیسه | کتابخانه/قرارداد نسخهدار و round-trip | Unit |
| callback تکراری پرداخت | دو callback با authority یکسان | یک Payment، Ledger و fulfillment | Integration/E2E |
| PDF فارسی | جدول، عدد و شناسه LTR | Font embedded، متن قابلاستخراج، visual | Integration/Manual |
| Locale leakage | دو کاربر fa/en پشت cache | پاسخ هر کاربر مطابق preference خودش | Integration/Security |
عیبیابی شکستهای i18n و L10n
| نشانه | علت محتمل | شاهد بعدی |
|---|---|---|
| کلید خام دیده میشود | Bundle ناقص، namespace یا fallback | resource version، key lookup trace |
| فقط CI متن متفاوت دارد | نسخه CLDR/ICU، OS، font یا timezone | image/runtime/tzdata/font identity |
| تاریخ یک روز جابهجا است | UTC/local، parse بدون zone یا calendar | raw instant، zone، calendar و formatter |
| جستوجوی «ی» نتیجه ندارد | Normalization/Index ناسازگار | code points، analyzer و index version |
| پرانتز/شناسه برعکس است | نبود base direction یا isolation | DOM lang/dir/bdi و clipboard text |
| مبلغ ده برابر است | تبدیل دوباره یا واحد ضمنی | value+unit در هر hop و ledger |
| ترجمه کاربر دیگری دیده میشود | cache key فاقد Locale/tenant | request headers، cache key، user preference |
| Screenshot شکست خورده ولی UI سالم است | font/anti-aliasing محیط | pixel diff، font hash و semantic assertions |
گزارش شکست باید Locale resolved، منبع انتخاب، timezone، calendar، numbering system، resource version، tzdata/ICU/CLDR، browser/OS، run ID و دادهٔ حساسزداییشده را ثبت کند.
حاکمیت و مالکیت کیفیت بومیسازی
- Product/Market owner: بازار، واحد، محتوای حقوقی و ریسک پذیرفتهشده؛
- Engineering: قرارداد canonical، fallback، formatter/parser و observability؛
- Design: Layout انعطافپذیر، typography و componentهای bidirectional؛
- Localization/Linguist: واژهنامه، لحن، Context و LQA؛
- QA: ماتریس ریسک، Oracle، coverage و evidence؛
- Security/Accessibility: کنترلهای تخصصی برای جریانهای مربوط؛
هر Locale یک DRI، وضعیت Launch و Exit criteria میخواهد. «ترجمه ۱۰۰٪» به معنی آمادگی انتشار نیست؛ ممکن است همهٔ کلیدها ترجمه شده باشند اما پرداخت، Screen Reader یا PDF کار نکند.
معیارهایی که واقعاً کمک میکنند
- نرخ کلید مفقود و Fallback در Runtime، به تفکیک Locale و صفحه؛
- نقصهای تولید بر اساس دسته: معنا، جهت، قالب، Parser، داده، cache یا accessibility؛
- زمان رفع نقص Locale و سهم Reopen؛
- پوشش جریانهای پرریسک در برابر ماتریس، نه تعداد کل Screenshot؛
- نرخ شکست Pseudo-localization و Placeholder gate؛
- اختلاف نسخه Resource/CLDR/ICU/tzdata بین محیطها؛
- درصد Visual diffهای معنادار در برابر نویز؛
- سن و مالک Gapهای پذیرفتهشدهٔ هر بازار.
تعداد کلمات ترجمهشده و درصد Automation شاخص ظرفیتاند، نه کیفیت. آنها را بدون Defect، Risk coverage و تجربهٔ کاربر به KPI موفقیت تبدیل نکنید.
برنامهٔ ۳۰روزهٔ اجرای تست فارسی و RTL
هفتهٔ اول: قرارداد و Baseline
جریانهای ورود، جستوجو، پرداخت، گزارش و اعلان را فهرست کنید. Locale contract، ترتیب انتخاب/fallback و مالک ریال/تومان، زمان و Normalization را تصویب کنید. پنج نقص اخیر را دستهبندی کنید.
هفتهٔ دوم: Gateهای ارزان
بررسی کلید/Placeholder، UTF-۸، متن هاردکد، CSS physical property و pseudo locales را به CI اضافه کنید. Corpus فارسی شامل ی/ک، ZWNJ، سه مجموعه رقم و متن Bidi بسازید.
هفتهٔ سوم: Oracleهای دامنه
Unit testهای زمان/پول/Normalization و Contract testهای API را تکمیل کنید. دو E2E حیاتی با Locale و timezone مشخص بسازید و نتیجهٔ مالی/دادهای را جدا از متن UI Assert کنید.
هفتهٔ چهارم: LQA و بازخورد تولید
یک LQA با متخصص فارسی و تست فناوری کمکی انجام دهید. Telemetry امن برای fallback و Locale resolution را بررسی، داشبورد متوازن را راهاندازی و Gapها را با Owner و تاریخ انقضا ثبت کنید.
اشتباهات رایج در تست i18n و L10n
- یکیگرفتن Language، Locale، Region، Direction، Calendar و Timezone؛
- اعلام «پشتیبانی فارسی» فقط به دلیل UTF-۸ یا یک صفحهٔ RTL؛
- راستچینکردن با CSS بدون
lang/dirمعنایی؛ - تبدیل همهٔ رقمها و شناسهها به فارسی بدون قرارداد فیلد؛
- نگاشت کور ی/ی و ک/ک روی Password، Token یا شناسه؛
- استفاده از offset ثابت به جای
Asia/Tehranو tzdata؛ - واگذاری ریال/تومان به Formatter و نبود واحد در API/Log؛
- اتصال تکهرشتهها و شرط جمع مخصوص انگلیسی؛
- Fallback ساکت که ترجمهٔ گمشده را تا تولید پنهان میکند؛
- ماتریس Cartesian عظیم بدون اولویت ریسک؛
- Screenshot بهعنوان تنها Oracle و ذخیره Baseline روی محیط ناپایدار؛
- فرض اینکه مترجم بومی همهٔ نقصهای فنی را مییابد؛
- تست فقط Web و فراموشی SMS، PDF، CSV، Search و Log؛
- استفاده از PII واقعی در Screenshot/Trace؛
- اعلام آمادگی بازار بر اساس «۱۰۰٪ ترجمه».
چکلیست نهایی انتشار Locale
- Language Tag استاندارد، جهت و ترتیب انتخاب/fallback نسخهدار است.
- مدل canonical از نمایش تاریخ، عدد و پول جداست.
- UTF-۸ در Web/API/DB/Queue/Export round-trip شده است.
- سیاست NFC و نگاشتهای دامنهای فارسی با مرز روشن تست شدهاند.
- ی/ی، ک/ک، ZWNJ، سه مجموعه رقم و متن Bidi در Corpus هستند.
- CSS منطقی، DOM/Focus و Componentهای RTL بررسی شدهاند.
- زمان با Instant، Calendar،
Asia/Tehranو tzdata نسخهدار سنجیده شده است. - ریال/تومان در هر hop واحد صریح و Oracle مالی مستقل دارد.
- Placeholder، plural/select، missing key و pseudo locale Gate دارند.
- فرم، Search، Sort، PDF، CSV، Email/SMS و Notification پوشش متناسب دارند.
- LQA فارسی، دسترسپذیری و امنیت Unicode انجام شدهاند.
- گزارش اجرا Locale/resource/runtime identity و دادهٔ امن دارد.
- Gap باقیمانده Owner، اثر و تاریخ بازنگری دارد.
پرسشهای متداول
تفاوت تست i18n و L10n چیست؟
تست i18n انعطاف معماری برای زبان، خط، Locale، قالب و جهتهای مختلف را میسنجد؛ تست L10n درستی ترجمه، قالب، محتوا و تجربهٔ یک بازار مشخص مانند فارسی ایران را بررسی میکند. اولی زیرساخت عمومی را هدف میگیرد و دومی کیفیت تطبیق واقعی را؛ هیچکدام جای دیگری نیست.
آیا fa-IR بهتنهایی تقویم شمسی و منطقهٔ زمانی تهران را تعیین میکند؟
خیر. fa-IR Language Tag است، اما Calendar، Timezone، Numbering system و ترجیح کاربر باید جداگانه در قرارداد و Runtime حل شوند. کاربر فارسی ممکن است خارج ایران باشد یا تقویم میلادی بخواهد. مقدار resolved هر بُعد را در شواهد تست ثبت کنید.
برای فارسی، ی و ک عربی را همیشه باید به فارسی تبدیل کنیم؟
نه بهصورت جهانی. برای Search نام یا متن نمایشی میتوان یک نگاشت دامنهای مصوب داشت، ولی Password، Token، امضا و شناسهٔ قراردادی ممکن است Exact match بخواهند. متن اصلی را در موارد لازم حفظ و نسخهٔ canonical را جدا تعریف کنید.
چگونه ریال و تومان را تست کنیم؟
مقدار و واحد را در هر مرز—UI، API، Database، Queue و Ledger—صریح کنید. سناریو باید تبدیل دقیق یکباره، جداکننده، ارقام، حدها، Refund و اثر مالی را بسنجد. Assertion متن «تومان» بدون Oracle روی مقدار canonical و Ledger کافی نیست.
آیا اتوماسیون و شبهبومیسازی جای بازبین فارسیزبان را میگیرند؟
خیر. ماشین کلید مفقود، Placeholder، Overflow، Parser، جهت و Regression را سریع پیدا میکند؛ انسان معنا، لحن، حساسیت فرهنگی، خوانایی و تجربه را داوری میکند. بهترین استراتژی، Gateهای خودکار ارزان در هر تغییر و LQA انسانی هدفمند پیش از انتشار است.
جمعبندی: Locale را به یک قرارداد قابلآزمون تبدیل کنید
تست بومیسازی با ترجمهٔ چند صفحه تمام نمیشود. Language Tag، جهت، Unicode، Normalization، ورودی، Search، تقویم، زمان، پول، کانال و Resource version باید از مدل canonical تا تجربهٔ کاربر قابلردیابی باشند. برای ایران، ی/ک، نیمفاصله، سه مجموعه رقم، Bidi، تقویم شمسی، Asia/Tehran و مرز ریال/تومان سناریوهای درجهیکاند، نه جزئیات ظاهری.
از یک جریان پرریسک مانند پرداخت شروع کنید: قرارداد Locale را بنویسید، Corpus فارسی بسازید، Oracle UI را به API/Ledger وصل کنید و Pseudo-RTL را در CI اجرا کنید. وقتی هر شکست Locale identity، نسخه و شاهد قابلبازتولید داشته باشد، تیم بهجای شکار Screenshotهای پراکنده میتواند درباره ریسک انتشار تصمیم بگیرد.

