لماذا تحتاج رسائل التحقق من صحة النماذج بالعربية إلى مراجعة خاصة بها
لماذا تبدو نصوص التحقق من الصحة المترجمة حرفيًا إلى العربية مصطنعة، وكيف تُفسد فئات الجمع الست في CLDR جملة بسيطة مثل "لديك N أخطاء"، وأكواد جاهزة تراعي اتجاه RTL لـ react-hook-form وZod وICU MessageFormat.
نموذج يتحقق من صحة الإدخال بشكل سليم بالعربية، ونموذج تُقرأ نصوصه بشكل سليم بالعربية — هذان أمران مختلفان تمامًا، ومعظم جولات ضمان الجودة تتحقق من الأول فقط. تعمل قاعدة التحقق من البريد الإلكتروني، ويتحقق الحقل الإلزامي من نفسه، ويظهر إطار أحمر حول الحقل الصحيح — لكن الرسالة الظاهرة بجواره جملة يستطيع متحدث عربي أصلي قراءتها دون عناء، لكنه لن يكتبها بنفسه أبدًا: تركيب جملة منقول حرفيًا عن الإنجليزية، وتمييز بين المفرد والجمع لا يطابق الطريقة التي تُعدّ بها الأشياء في العربية، وأيقونة ملتصقة بالحافة اليسرى الفيزيائية للحقل لأن هذا هو المكان الذي وضعها فيه تصميم إنجليزي الاتجاه.
لا شيء من هذا يظهر في اختبار وظيفي. ما يظهر هو منتج يبدو غريبًا عن الجمهور الذي وُطِّن من أجله، جملة تلو الأخرى، وتحديدًا في الجزء من الواجهة الذي يكون فيه المستخدم محبطًا بالفعل. هذا المقال يتناول طبقة المحتوى وتجربة المستخدم في التحقق من صحة النماذج بالعربية تحديدًا: كتابة نصوص أخطاء تُقرأ كنثر عربي أصيل لا كإنجليزية مُترجمة، والتعامل الصحيح مع قواعد الجمع في العربية حين تتضمن الرسالة عددًا، وتموضع رسائل الخطأ المضمّنة وأيقوناتها بحيث تُحل بشكل صحيح في اتجاه RTL دون ورقة أنماط ثانية معكوسة يدويًا. يفترض المقال أن حقولك مضبوطة مسبقًا على اتجاه الأساس الصحيح — فدليلنا في اختبار أخطاء RTL يغطي بالفعل فرض direction: ltr على حقول البريد الإلكتروني والهاتف وكلمة المرور داخل نموذج عربي، لذا لن نكرر ذلك هنا.
أبرز النقاط
- تُحلّ الأعداد الأصلية في العربية إلى ست فئات نحوية مختلفة —
zeroوone وtwo وfew وmany وother — لا الفئتين المستخدمتين في الإنجليزية، وتوثيق MDN لواجهةIntl.PluralRulesنفسه يستخدم العربية تحديدًا لتوضيح الفئات الست جميعها.- الحدود الدقيقة موثّقة لا تقريبية: وفق مخطط قواعد الجمع للغات في CLDR التابع لمعيار يونيكود، تغطي فئة
fewفي العربيةn % 100 = 3..10وفئةmanyتغطيn % 100 = 11..99— أي أن 6 أخطاء و106 أخطاء تقعان في الفئة نفسهاfew، بينما 15 و115 تقعان معًا فيmany.- عبارة شرطية كُتبت أصلًا للإنجليزية (
count === 1 ? 'error' : 'errors') تُختزل بصمت أربعًا من فئات العربية الست في نص واحد خاطئ — والخطأ لا يظهر في مراجعة الشيفرة لأن الكود يُصرَّف والنسخة الإنجليزية تعمل كما هي.- حجّة
pluralفي صيغة ICU MessageFormat والمكتبات المبنية عليها — مثل FormatJS وi18next — تتيح للمترجم كتابة نسخة مستقلة لكل فئة من فئات CLDR بدل فرض جملة واحدة مُقولَبة على الفئات الست جميعها.- مكتبات التحقق من الصحة لا تُوطّن ملخصات الأعداد نيابةً عنك. تخصيص الأخطاء في Zod وجسور مجتمعية مثل
zod-i18n-mapتترجم رسائل الحقول الفردية؛ أما ملخص من نوع "لديك N أخطاء" فهو شيفرة تطبيق تكتبها بنفسك، وهو بالضبط المكان الذي يجب أن يعيش فيه منطق فئات الجمع.- معيار النجاح WCAG 3.3.1 يشترط أن يُوصَف الخطأ للمستخدم "في نص"، لا بأيقونة أو إطار أحمر فقط، وإرشادات W3C نفسها حول إشعارات النماذج المضمّنة توصي بأن تظهر الملاحظات "عند عنصر النموذج أو قريبًا منه" — ولا شيء من هذا خاص باتجاه RTL، فترجمة ذلك إلى CSS منطقي (logical) متروكة لك، وهذا ما يفعله هذا المقال.
لماذا يمكن أن تبقى الترجمة الصحيحة تبدو خاطئة
معظم نصوص التحقق من الصحة بالعربية في الإنتاج بدأت حياتها كجملة إنجليزية سُلّمت لمترجم، وترجمة سليمة ودقيقة لعبارة "Please enter a valid email address" قد تصبح يرجى إدخال عنوان بريد إلكتروني صالح — سليمة نحويًا ولا لبس في كونها ترجمة. أما نصوص الأخطاء الأصيلة في المنتجات المبنية بالعربية أولًا فتميل إلى أن تكون أقصر وأكثر مباشرة وأقل تكلفًا من عادة "يرجى" الإنجليزية: جملة مثل أدخل بريدًا إلكترونيًا صالحًا تُقرأ كتعليمة يكتبها شخص فعلًا، لا كجملة رُكّبت بنقل ترتيب الجملة الإنجليزية إلى كلمات عربية.
والعلامة الفارقة عادة تركيبية قبل أن تكون معجمية. تبني الإنجليزية رسائل التحقق من الصحة حول تركيب مبني للمجهول ومُخفَّف الحدة — "this field is required"، "the password must contain at least 8 characters" — لأن أعراف كتابة تجربة المستخدم بالإنجليزية تفضّل الأسلوب غير المباشر. نقل هذا التركيب حرفيًا إلى العربية ينتج جملة مثل يجب أن يحتوي هذا الحقل على 8 أحرف على الأقل، وهي سليمة لكنها أكثر رسمية وتعقيدًا مما يكتبه شخص يصوغ النص العربي من الصفر: جملة كلمة المرور يجب أن تتكون من 8 أحرف على الأقل تبدو أكثر طبيعية لأنها تبدأ بالموضوع الفعلي (كلمة المرور) بدل مفهوم مجرّد مثل "هذا الحقل"، بما يوافق الطريقة التي تُبنى بها الجملة العربية عادة حول موضوع واضح. لا تحمل أي من الصيغتين خطأً نحويًا. إحداهما تُقرأ كنص مؤلَّف، والأخرى تُقرأ كنص مرّ عبر الإنجليزية في طريقه إلى الشاشة.
الحل ليس تحسين توجيهات المترجم فحسب، رغم أن ذلك يساعد — بل معاملة نص الأخطاء بالعربية كجولة مراجعة قائمة بذاتها، لا كبند عابر ضمن مراجعة الترجمة. اطلب من شخص أن يقرأ كل نص تحقق بصوت عالٍ ومنفصلًا عن سياقه، ويسأل نفسه: هل هذه جملة كان سيكتبها من تلقاء نفسه؟ إن كانت الإجابة "لا، لكنها سليمة تقنيًا"، فالنص يحتاج إلى إعادة صياغة، لا إلى اعتماد.
العربية لا تملك صيغتي جمع فقط — بل ست
الجمع في الإنجليزية ثنائي: صيغة واحدة للعدد واحد بالضبط، وأخرى لكل ما عداه، بما فيه الصفر. جملة ملخّص مثل "You have 1 error" / "You have 2 errors" لا تحتاج إلا هذا الفرع الواحد، ولهذا فإن عبارة شرطية مثل count === 1 ? 'error' : 'errors' حل كافٍ تمامًا بالإنجليزية، وحل معطوب فعليًا بمجرد إعادة استخدامه للعربية.
تُحلّ الأعداد الأصلية في العربية إلى ست فئات نحوية مختلفة. هذا ليس تقريبًا — بل أمر محدد بدقة. توثيق قواعد الجمع في CLDR التابع ليونيكود يُعرّف الوسوم الرمزية الست المستخدمة عبر كل لغة يغطيها CLDR — zero وone وtwo وfew وmany وother — ومخطط قواعد الجمع للغات الخاص بيونيكود نفسه يقدّم القاعدة الدقيقة لكل فئة في العربية:
| الفئة | القاعدة | أمثلة على الأعداد |
|---|---|---|
| zero | n = 0 | 0 |
| one | n = 1 | 1 |
| two | n = 2 | 2 |
| few | n % 100 = 3..10 | 3–10، 103–110 |
| many | n % 100 = 11..99 | 11–99، 111 |
| other | كل ما تبقى | 100، 200، 1000 |
الجزء الذي تفوّته معظم التطبيقات المبنية على منطق إنجليزي أولًا هو شرط باقي القسمة: فئتا few وmany تتكرران كل مئة، فنموذج فيه 106 أخطاء يقع في الفئة النحوية نفسها التي يقع فيها نموذج فيه 6 أخطاء، ونموذج فيه 115 خطأ يقع في الفئة نفسها التي يقع فيها 15 خطأ. أما العدد 100 بالضبط فيقع في other لا في many، لأن 100 % 100 = 0.
الواجهة البرمجية التي تكشف هذا السلوك وقت التشغيل هي Intl.PluralRules، وتوثيق MDN نفسه يستخدم العربية تحديدًا ليُظهر أن لغة ما يمكن أن تستخدم الفئات الست كلها، بينما لا تُعيد الإنجليزية سوى one أو other:
const arRules = new Intl.PluralRules('ar-EG');
for (const n of [0, 1, 2, 6, 18, 100]) {
console.log(n, '→', arRules.select(n));
}
// 0 → "zero"
// 1 → "one"
// 2 → "two"
// 6 → "few"
// 18 → "many"
// 100 → "other"
العبارة الشرطية المكتوبة للإنجليزية تختزل خمسًا من هذه الفروع الست في نص واحد، والخطأ يختبئ جيدًا: الكود يُصرَّف، والنسخة الإنجليزية تعمل، ولا يظهر الخلل إلا حين يقرأ متحدث عربي أصلي رسالة تعامل 3 أخطاء و30 خطأ بالطريقة نفسها، أو تعرض حالة المثنى (عددان بالضبط) وكأنها جمع عادي.
كتابة الصيغ الست دون أن تبدو كجدول مواصفات
معرفة الفئات لا تُنتج جملًا طبيعية تلقائيًا — ذلك يتطلب مراجعة متعمّدة لكل فئة على حدة، لأن مطابقة العدد والمعدود في العربية تُغيّر صيغة الاسم نفسه، لا مجرد الرقم المُدرج داخل الجملة. وفق مراجع نحو العربية القياسية، تأخذ الأعداد من 3 إلى 10 المعدود جمعًا مجرورًا، بينما تأخذ الأعداد من 11 إلى 99 المعدود مفردًا منصوبًا — وهو تحوّل نحوي لا مقابل له في الإنجليزية. أما تقويلب جملة واحدة والاكتفاء بتبديل الرقم، على طريقة الأصل الإنجليزي، فينتج بالضبط ذلك النوع من العربية السليمة تقنيًا لكن الركيكة أسلوبيًا الذي ورد وصفه أعلاه.
خريطة رسائل مفهرسة بفئات CLDR، كتبها شخص يصوغ كل جملة بدل تقويلب واحدة:
const arRules = new Intl.PluralRules('ar-EG');
const errorCountMessage = {
zero: () => 'لا توجد أخطاء في النموذج.',
one: () => 'يوجد خطأ واحد في النموذج.',
two: () => 'يوجد خطآن في النموذج.',
few: (n) => `توجد ${n} أخطاء في النموذج.`,
many: (n) => `يوجد ${n} خطأً في النموذج.`,
other: (n) => `يوجد ${n} خطأ في النموذج.`,
};
function formatErrorCount(n) {
return errorCountMessage[arRules.select(n)](n);
}
تُطابق الصيغ الست نفسها مباشرةً حجّة plural في صيغة ICU MessageFormat، وهي الصياغة التي تُبنى عليها FormatJS وعدة أدوات توطين أخرى. يُشير دليل ICU نفسه إلى أن المترجم "يحتاج إلى إضافة one وfew وغيرها حسب الضرورة لكل لغة هدف" — فالفئات ليست زخرفة اختيارية، بل هي ما صُمّمت الصيغة أصلًا ليملأه المترجم:
{errorCount, plural,
zero {لا توجد أخطاء في النموذج}
one {يوجد خطأ واحد في النموذج}
two {يوجد خطآن في النموذج}
few {توجد # أخطاء في النموذج}
many {يوجد # خطأً في النموذج}
other {يوجد # خطأ في النموذج}
}
الرمز # هو العنصر النائب في ICU لقيمة حجّة الجمع نفسها، مُنسّقة كرقم، لذا لا حاجة لتمريرها مجددًا كمتغيّر منفصل.
إن كانت بنيتك التقنية تعتمد i18next بدل ذلك، تُعرَض الفئات الست نفسها كلواحق لمفاتيح الترجمة بدل كتلة مضمّنة، وتوثيق i18next الخاص بالجمع يستخدم العربية بالاسم كمثال على لغة تحتاج المجموعة الموسّعة من الصيغ، ويذكر أن للعربية "5 صيغ جمع إضافة إلى المفرد":
{
"errorCount_zero": "لا توجد أخطاء في النموذج",
"errorCount_one": "يوجد خطأ واحد في النموذج",
"errorCount_two": "يوجد خطآن في النموذج",
"errorCount_few": "توجد {{count}} أخطاء في النموذج",
"errorCount_many": "يوجد {{count}} خطأً في النموذج",
"errorCount_other": "يوجد {{count}} خطأ في النموذج"
}
أيًا كانت الصياغة التي تعتمدها، ضع النصوص الست جميعها أمام مراجع عربي حقيقي معًا، لا فئة other الاحتياطية فقط التي يميل المطوّر إلى تفقدها بعينه. من الشائع أن تُهمَل فئتا zero وtwo تحديدًا في جولة ترجمة متعجّلة، لأن الإنجليزية لا تملك فرعًا مقابلًا يُذكّر أحدًا بوجودهما.
ربط ذلك بـ react-hook-form وZod
رسائل التحقق على مستوى الحقل — "يجب أن تتكوّن من 8 أحرف على الأقل"، "أدخل بريدًا إلكترونيًا صالحًا" — مشكلة منفصلة عن ملخص العدد أعلاه، وتحلّها معظم الفرق بطريقة مختلفة: تخصيص الأخطاء المدمج في Zod يدعم خريطة أخطاء واعية باللغة، وحزم مجتمعية مثل zod-i18n-map تربط هذه الخريطة بـ i18next بحيث يُترجم كل خطأ مدمج في Zod (مثل too_small وinvalid_string) تلقائيًا، بما في ذلك العربية:
import { z } from 'zod';
import i18next from 'i18next';
import { zodResolver } from '@hookform/resolvers/zod';
import { makeZodI18nMap } from 'zod-i18n-map';
import ar from 'zod-i18n-map/locales/ar/zod.json';
i18next.init({
lng: 'ar',
resources: { ar: { zod: ar } },
});
z.setErrorMap(makeZodI18nMap());
const signupSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
const form = useForm({
resolver: zodResolver(signupSchema),
});
يغطي هذا الرسالة المرتبطة بكل حقل على حدة. لكنه لا يغطي ملخصًا يُحصي عدد الحقول الخاطئة — "لديك 3 أخطاء أعلاه" — لأن هذا النص لا تُصدره Zod أصلًا؛ بل يجمعه مكوّن النموذج الخاص بك من Object.keys(form.formState.errors).length. وهذا بالضبط العدد الذي يحتاج إلى التنسيق الواعي بقواعد CLDR من القسم السابق، ومن السهل إغفاله تحديدًا لأنه يعيش في شيفرة التطبيق، طبقة فوق مكتبة التحقق من الصحة التي وُطّنت بالفعل:
function ErrorSummary({ errors }: { errors: FieldErrors }) {
const count = Object.keys(errors).length;
if (count === 0) return null;
return <p role="alert">{formatErrorCount(count)}</p>;
}
إن كانت بنيتك التقنية تعرض الرسائل عبر FormatJS أو i18next بدل دالة مكتوبة يدويًا، استبدل formatErrorCount باستدعاء لأي منسّق مبني على ICU يستخدمه تطبيقك بالفعل — الفكرة أن نص الملخص، على عكس رسائل Zod الخاصة بكل حقل، لا توجد مكتبة تتكفّل بجعله صحيحًا وفق CLDR بالعربية نيابةً عنك.
تموضع رسائل الخطأ وأيقوناتها بحيث تُحل بشكل صحيح في RTL
لا يُكتب أيٌّ من أكثر المصدرين استشهادًا بشأن تموضع رسائل الخطأ مع وضع اتجاه RTL في الاعتبار، ما يعني أن إرشاداتهما تحتاج إلى ترجمة إلى CSS محايد الاتجاه بدل نسخها كتموضع فيزيائي مباشر. توصي إرشادات Nielsen Norman Group بشأن أخطاء النماذج بإظهار "رسالة خطأ قابلة للتنفيذ أسفل الحقل المعطوب أو بجواره" وإقران ذلك بـ"أيقونة إلى يسار رسالة الخطأ" لتحسين إمكانية المسح البصري ومساعدة مستخدمي عمى الألوان. وتقول دروس W3C نفسها حول الإشعارات المضمّنة إن الملاحظات ينبغي أن تظهر "عند عناصر التحكم في النموذج أو بالقرب منها". كلا الإرشادين صحيح، لكنه مصاغ افتراضيًا بمصطلحات LTR — فكلمة "إلى اليسار" تعليمة فيزيائية يجب أن تتحول إلى تعليمة منطقية قبل أن تكون صحيحة بالعربية.
الحل الميكانيكي هو نفسه الذي يتكرر في كل قرار تخطيط RTL: التوقف عن كتابة left/right وترك المتصفح يحلّ الموضع انطلاقًا من dir. إقران أيقونة بنص الخطأ داخل صف مرن (flex) يحقق ذلك تلقائيًا، لأن حافة بداية صف flex تتبع أصلًا اتجاه السطر الخطي للمستند لا جانبًا فيزيائيًا ثابتًا:
.field-error {
display: flex;
align-items: center;
gap: 0.375rem;
color: var(--color-error);
margin-block-start: 0.25rem;
}
.field-error-icon {
flex-shrink: 0;
/* لا margin-left ولا margin-right — تكفي gap مع حلّ حافة
البداية الخاص بـ flex لإبقاء الأيقونة في الجانب الصحيح
في كلا الاتجاهين دون تجاوز [dir="rtl"]. */
}
أما الأيقونة المتموضعة absolute داخل الحقل نفسه — علامة صح أو رمز خطأ داخل الحقل لا بجواره — فهي الحالة التي تتعطّل فعليًا في الممارسة العملية، لأن قيمة مثل right: 12px منسوخة مباشرة من تصميم إنجليزي تعليمة فيزيائية لا تنعكس أبدًا:
/* خطأ: دائمًا الحافة النهائية للحقل في LTR، ودائمًا الحافة
الخاطئة بمجرد أن تُعرض الصفحة بـ dir="rtl". */
.input-wrapper .status-icon {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
}
/* صحيح: يُحل دائمًا إلى الحافة النهائية الفعلية، وفق مواصفة
CSS Logical Properties and Values. */
.input-wrapper .status-icon {
position: absolute;
inset-inline-end: 12px;
top: 50%;
transform: translateY(-50%);
}
الخاصية inset-inline-end جزء من مواصفة CSS Logical Properties and Values — عائلة الخصائص نفسها التي يتناولها مقالنا في نظام التصميم بخصوص التخطيط عمومًا، مطبَّقة هنا تحديدًا على نمط الأيقونة داخل الحقل الذي تعتمد عليه حالات الخطأ.
التموضع وحده لا يُلبّي متطلبات إمكانية الوصول، أيًا كان الاتجاه الذي يُحل إليه. يشترط معيار النجاح WCAG 3.3.1 أن يُوصف الخطأ المكتشف للمستخدم "في نص"، مع توضيح صريح أنه "من المقبول تمامًا الإشارة إلى الخطأ... عبر صورة أو لون أو مؤشر بصري آخر، بالإضافة إلى الوصف النصي" — لا بدلًا منه أبدًا. عمليًا، يعني هذا أن كل أيقونة خطأ تحتاج إلى عنصر شقيق يحمل نصًا مرتبطًا بالحقل عبر aria-describedby، والحقل نفسه مُعلَّم بـ aria-invalid="true"، تمامًا كما يصف درس W3C حول الإشعارات — وهو شرط لا يتغير بين LTR وRTL، بل الذي يتغيّر هو CSS الذي يحدّد موضعه فقط.
أخطاء شائعة
تقويلب جملة عربية واحدة واستبدال الرقم فيها فقط. تطابق العدد والمعدود في العربية يُغيّر صيغة الاسم نفسه عبر الفئات، لا الرقم فقط — وقالب واحد بمتغيّر عدد مُدرج فيه هو الخطأ نفسه الذي ترتكبه عبارة شرطية إنجليزية بشرط count === 1، لكن بشكل أقل وضوحًا لمراجع لا يتحدث العربية.
إغفال فئتي zero وtwo عند الترجمة. لا تملك الإنجليزية فرعًا مقابلًا، فيميل المترجمون العاملون من جدول بيانات إنجليزي الأصل إلى تعبئة one وfew وmany وother وترك الفئتين الأخريين فارغتين أو مكررتين من other.
معاملة ترجمة سليمة على أنها ترجمة منتهية. العربية السليمة نحويًا التي تُحاكي تركيب الجملة الإنجليزية تُقرأ كترجمة، لا كنص مؤلَّف — وتحتاج إلى جولة مراجعة أسلوبية عربية أصيلة مستقلة عن التحقق من الدقة.
كتابة left/right بشكل ثابت على أيقونات الخطأ. هذه أكثر طريقة شائعة يتعطّل بها نموذج RTL سليم من نواحٍ أخرى بشكل مرئي، لأنها غير مرئية في مراجعة تصميم مقتصرة على LTR، ولا تظهر إلا حين يفتح أحدهم فعليًا النسخة العربية.
افتراض أن مكتبة التحقق من الصحة تُوطّن ملخصات الأعداد بنفسها. خريطة أخطاء Zod وجسورها إلى أدوات i18n تُعالج رسائل الحقول الفردية؛ أما نص "لديك N أخطاء" المُجمَّع يدويًا فهو شيفرة تطبيق، وهو بالضبط النص الأكثر عرضة لتشغيل منطق جمع إنجليزي على نص عربي.
إتقان التحقق من صحة النماذج بالعربية ليس مهمة ترجمة تُنجَز مرة واحدة ثم تُصان عبر جدول بيانات. إنها كتابة محتوى وقواعد نحو ومنطق تخطيط يجب مراجعتها معًا، من قِبل شخص يقرأ مجموعة النصوص ذات الفئات الست كنثر لا كمن يتحقق من أن قالبًا ما تُصرِّف بنجاح — المعيار نفسه الذي تُخضِع له بالفعل النسخة الإنجليزية، مطبّقًا على لغة نظام جمعها يملك فئات أكثر يجب إتقانها.