بنية التدويل للتطبيقات المبنية بالمحادثة: عرّف مفاتيح نص ثابتة، قواعد الجمع، وسير عمل ترجمة واحد يبقى متسقًا على الويب والهاتف المحمول.

أول شيء يتعطل ليس الشيفرة. هو الكلمات.
التطبيقات المبنية بالمحادثة Often start as a fast prototype: you type “Add a button that says Save”, the UI appears, and you move on. بعد أسابيع، تريد دعم الإسبانية والألمانية، فتكتشف أن تلك التسميات "المؤقتة" منتشرة عبر الشاشات والمكوّنات ورسائل البريد الإلكتروني ورسائل الخطأ.
تغييرات النص تحدث في العادة بتواتر أكبر من تغييرات الشيفرة. أسماء المنتجات تتغير، النصوص القانونية تتبدل، خطوات الإعداد تُعاد صياغتها، وفريق الدعم يطلب رسائل خطأ أوضح. إذا كان النص موجودًا داخل كود واجهة المستخدم مباشرةً، فإن كل تعديل بسيط يصبح إصدارًا محفوفًا بالمخاطر، وستفوت أماكن حيث تُصاغ نفس الفكرة بعبارات مختلفة.
إليك الأعراض المبكرة التي تشير إلى أنك تبني دين ترجمة:
مثال واقعي: تبني نظام CRM بسيط في Koder.ai. تطبيق الويب يقول "Deal stage"، بينما تطبيق الجوال يقول "Pipeline step"، وتنبيه خطأ يقول "Invalid status". حتى لو تُرجمت الثلاثة، سيشعر المستخدمون بعدم التناسق لأن المفاهيم لا تتطابق.
"التناسق" لا يعني "نفس الأحرف في كل مكان". يعني:
عندما تعامل النص كبيانات منتج وليس كزينة، يتوقف إضافة لغات عن أن يكون فوضى ويصبح جزءًا روتينيًا من البناء.
التدويل (i18n) هو العمل الذي تفعله حتى يدعم التطبيق لغات متعددة دون إعادة كتابة كبيرة. التوطين (l10n) هو المحتوى الفعلي للغة ومنطقة معينة—مثل الفرنسية (كندا) بالكلمات والتنسيقات والنبرة المناسبة.
هدف بسيط: كل قطعة من النص المواجه للمستخدم تُستدعى عبر مفتاح ثابت، لا تُكتب مباشرة في كود واجهة المستخدم. إذا استطعت تغيير جملة دون فتح مكوّن React أو ويدجت Flutter، فأنت على الطريق الصحيح. هذه هي جوهر معمارية التدويل للتطبيقات المبنية بالمحادثة، حيث من السهل بطريق الخطأ شحن نصوص مضمنة ناتجة عن جلسة دردشة.
النص المواجه للمستخدم أوسع مما تتوقع الفرق عادة. يشمل الأزرار، التسميات، أخطاء التحقق، حالات الشاشة الفارغة، نصائح الإعداد، إشعارات الدفع، رسائل البريد الإلكتروني، تصدير ملفات PDF، وأي رسالة يمكن للمستخدم رؤيتها أو سماعها. عادةً لا يشمل سجلات داخلية، أسماء أعمدة قواعد البيانات، معرفات أحداث التحليلات، أعلام الميزات، أو مخرجات تصحيح خاصة بالمشرف.
أين يجب أن تعيش الترجمات؟ في الممارسة العملية، غالبًا في كل من الواجهة الأمامية والخلفية، مع حدود واضحة:
الخطأ الذي يجب تجنبه هو خلط المسؤوليات. إذا أعاد الخادم جمل إنجليزية مكتوبة بالكامل لأخطاء الواجهة، فلن يستطيع الfrontend ترجمتها بشكل نظيف. نمط أفضل هو: يعيد الخادم رمز خطأ (وربما معبار آمن)، والعميل يربط ذلك الرمز برسالة مترجمة.
امتلاك النص قرار منتج، ليس تفصيلًا تقنيًا. قرر مبكرًا من يمكنه تغيير الكلمات والموافقة على النبرة.
إذا كانت ملكية النص للمنتج، عامل الترجمات كمحتوى: نسخه، مراجعته، ومنح المنتج طريقة آمنة لطلب التغييرات. إذا كانت الملكية للهندسة، ضع قاعدة أن أي سلسلة واجهة مستخدم جديدة يجب أن تأتي مع مفتاح وترجمة افتراضية قبل أن تُشحن.
مثال: إذا كانت عملية التسجيل تقول "Create account" في ثلاث شاشات مختلفة، اجعلها مفتاحًا واحدًا مستخدمًا في كل مكان. هذا يحافظ على المعنى متسقًا، يجعل المترجمين أسرع، ويمنع تغييرات صغيرة في الصياغة من أن تتحول إلى تنظيف عبر عدة شاشات لاحقًا.
المفاتيح هي العقدة بين واجهتك وترجماتها. إذا ظل هذا العقد يتغير، ستحصل على نص مفقود، تصحيحات مستعجلة، وصياغات غير متسقة عبر الويب والهاتف المحمول. قاعدة جيدة واحدة: المفاتيح يجب أن تصف المعنى، لا الجملة الإنجليزية الحالية.
استخدم معرّفات ثابتة كمفاتيح (مثل billing.invoice.payNow) بدلًا من النسخة الكاملة من النص (مثل "Pay now"). مفاتيح الجمل تنهار عندما يقوم شخص ما بتعديل الصياغة أو يضيف ترقيمًا أو يغيّر الحالة.
نمط عملي قابل للقراءة: شاشة (أو مجال) + مكوّن + القصد. اجعلها مملة ومتوقعًة.
أمثلة:
auth.login.titleauth.login.emailLabelbilling.checkout.payButtonnav.settingserrors.network.offlineاختَر متى تُعيد استخدام مفتاح مقابل إنشاء مفتاح جديد بالسؤال: "هل المعنى متطابق في كل مكان؟" أعد استخدام المفاتيح للإجراءات العامة حقًا، لكن قسّم المفاتيح عندما يتغير السياق. مثلاً، "Save" في شاشة الملف الشخصي قد تكون إجراء بسيط، بينما "Save" في محرر معقد قد يحتاج إلى نبرة مختلفة في بعض اللغات.
حافظ على نص واجهة المستخدم المشترك في أسماء مساحات مخصصة حتى لا يتكرر عبر الشاشات. دلائل شائعة تعمل جيدًا:
common.actions.* (save, cancel, delete)common.status.* (loading, success)common.fields.* (search, password)errors.* (validation, network)nav.* (tabs, menu items)عندما يتغير النص لكن يبقى المعنى نفسه، احتفظ بالمفتاح وحدث القيم المترجمة فقط. هذا هو الهدف كله من المعرّفات الثابتة. إذا تغيّر المعنى (حتى قليلاً)، أنشئ مفتاحًا جديدًا واترك القديم قائمًا حتى تتأكد أنه غير مستخدم. هذا يتجنب حالات "الترجمة الصامتة" حيث تكون الترجمة موجودة تقنيًا لكنها الآن خاطئة.
مثال صغير من تدفق على غرار Koder.ai: دردشتك تُولد تطبيق React للويب وFlutter للجوال. إذا استخدم كلاهما common.actions.save، ستحصل على ترجمات متسقة في كل مكان. لكن إذا استخدم الويب profile.save والجوال account.saveButton، ستتباعد مع مرور الوقت، حتى لو بدا النص الإنجليزي متطابقًا اليوم.
عامل لغة المصدر (غالبًا الإنجليزية) كمصدر واحد للحقيقة. احتفظ بها في مكان واحد، راجعها كأنها كود، وتجنب أن تظهر السلاسل في مكونات عشوائية "لمجرد الآن". هذه أسرع طريقة لتجنب النصوص المضمنة وإعادة العمل لاحقًا.
قاعدة بسيطة تساعد: التطبيق قد يعرض نصًا من نظام i18n فقط. إذا احتاج شخص لنص جديد، يضيف مفتاحًا ورسالة افتراضية أولًا، ثم يستخدم ذلك المفتاح في الواجهة. هذا يحافظ على استقرار معمارية التدويل للتطبيقات المبنية بالمحادثة حتى عندما تنتقل الميزات.
إذا كنت تُصدر الويب والهاتف المحمول، تريد كتالوج مفاتيح مشترك واحد، بالإضافة إلى مساحة لفرق الميزات للعمل بدون التداخل. ترتيب عملي:
حافظ على المفاتيح متطابقة عبر المنصات، حتى لو اختلف التنفيذ (React على الويب، Flutter على الجوال). إذا استخدمت منصة مثل Koder.ai لتوليد كلا التطبيقين من الدردشة، يصبح تصدير الشيفرة أسهل للصيانة عندما تشير المشاريع إلى نفس أسماء المفاتيح ونفس صيغة الرسالة.
الترجمات تتغير مع الوقت. عامل التغييرات كتغييرات منتج: صغيرة، مراجعة، وقابلة للتتبع. مراجعة جيدة تركز على المعنى وإعادة الاستخدام، ليس فقط الإملاء.
لمنع تباعد المفاتيح بين الفرق، اجعل المفاتيح مملوكة للميزات (billing., auth.)، ولا تعيد تسمية المفاتيح لمجرد تغيير الصياغة. حدِّث الرسالة، احتفظ بالمفتاح. المفاتيح معرفات، ليست نسخًا من النص.
قواعد الجمع تختلف حسب اللغة، لذا نمط الإنجليزية البسيط (1 مقابل الباقي) ينهار بسرعة. بعض اللغات لها صيغ منفصلة للصفر، واحد، 2-4، والعديد. أخرى تغير الجملة بأكملها، لا الاسم فقط. إذا خبزت منطق الجمع داخل الواجهة بشرطيات if-else، سينتهي بك الأمر بتكرار النص وفقدان حالات الحافة.
نهج أأمن هو الاحتفاظ برسالة مرنة واحدة لكل فكرة وترك طبقة i18n تختار الشكل الصحيح. رسائل نمط ICU مصممة لذلك. تبقي قرارات النحو عند المترجمين، وليس في مكوناتك.
هنا مثال صغير يغطي الحالات التي ينسى الناس ذكرها:
itemsCount = "{count, plural, =0 {No items} one {# item} other {# items}}"
ذلك المفتاح الواحد يغطي 0 و1 وكل الباقي. يمكن للمترجمين استبداله بأشكال الجمع المناسبة للغتهم دون أن تلمس الشيفرة.
عندما تحتاج صياغة مبنية على الجنس أو الدور، تجنب إنشاء مفاتيح منفصلة مثل welcome_male وwelcome_female إلا إذا كان المنتج يتطلب ذلك فعلًا. استخدم select حتى تبقى الجملة وحدة واحدة:
welcomeUser = "{gender, select, female {Welcome, Ms. {name}} male {Welcome, Mr. {name}} other {Welcome, {name}}}"
لتجنب الوقوع في مربعات القواعد الإعرابية، اجعل الجمل كاملة قدر الإمكان. لا تَركّب مقاطع مثل "{count} " + t('items') لأن العديد من اللغات لا تسمح بإعادة ترتيب الكلمات بهذه الطريقة. فضّل رسالة واحدة تتضمن الرقم والاسم والكلمات المحيطة.
قاعدة بسيطة تعمل جيدًا في التطبيقات المبنية بالمحادثة (بما في ذلك مشاريع Koder.ai): إذا احتوت الجملة على رقم أو شخص أو حالة، اجعلها ICU منذ اليوم الأول. تكلف قليلًا أكثر مقدمًا وتوفّر الكثير من دين الترجمة لاحقًا.
إذا احتفظ تطبيق React على الويب وFlutter على الجوال بكل ملفاتهما للترجمة محليًا، سيتباعدان. نفس الزر ينتهي بكلمات مختلفة، مفتاح يعني شيئًا على الويب وشيئًا آخر على الجوال، وتبدأ تذاكر الدعم بذكر "التطبيق يقول X لكن الموقع يقول Y".
الإصلاح الأبسط والأهم: اختر مصدر حقيقة واحد للصيغة وتعامل معه ككود. لمعظم الفرق، يعني ذلك مجموعة مشتركة واحدة من ملفات locale (مثلاً JSON بصيغ رسائل ICU) يستهلكها الويب والهاتف.
إعداد عملي هو حزمة "i18n" صغيرة أو مجلد يحتوي على:
React وFlutter يصبحان مستهلكين. لا ينبغي لهما اختراع مفاتيح جديدة محليًا. في سير عمل على غرار Koder.ai (React web، Flutter mobile)، يمكنك توليد كلا العميلين من نفس مجموعة المفاتيح، والحفاظ على التغييرات تحت مراجعة كما أي تغيير كود آخر.
مواءمة الخلفية جزء من نفس القصة. لا ينبغي أن تكون الأخطاء والإشعارات والبريد اليدوي مكتوبة بالإنجليزية في Go. بدلاً من ذلك، أعد رموز أخطاء ثابتة (مثل auth.invalid_password) بالإضافة إلى أي معلمات آمنة. بعدها تربط العملاء الرموز بالنصوص المترجمة. بالنسبة لرسائل البريد التي ترسل من الخادم، يمكن للخادم أن يرسم القوالب باستخدام نفس المفاتيح وملفات locale.
اصنع كتاب قواعد صغير وطبّقه بمراجعة الكود:
لمنع مفاتيح مكررة بمعانٍ مختلفة، أضف حقل "وصف" (أو ملف تعليقات) للمترجمين ولمستقبلك. مثال: billing.trial_days_left يجب أن يوضح إن كان يُعرض كبانر، بريد إلكتروني، أم كلاهما. جملة واحدة غالبًا توقف إعادة الاستخدام "قريبة بما فيه الكفاية" التي تخلق دين ترجمة.
هذا الاتساق هو العمود الفقري لمعمارية التدويل للتطبيقات المبنية بالمحادثة: مفردات مشتركة، أسطح متعددة، ولا مفاجآت عند شحن لغة جديدة.
معمارية التدويل الجيدة للتطبيقات المبنية بالمحادثة تبدأ ببساطة: مجموعة مفاتيح رسالة واحدة، مصدر حقيقة واحد للنص، ونفس القواعد على الويب والهاتف. إذا كنت تبني بسرعة (مثلاً باستخدام Koder.ai)، تحافظ هذه البنية على السرعة دون خلق دين ترجمة.
اختر لغاتك مبكرًا وقرر ماذا يحدث عندما تكون ترجمة مفقودة. خيار شائع: عرض لغة المستخدم المفضلة إن وجدت، وإلا السقوط على الإنجليزية، وتسجيل المفاتيح المفقودة حتى تصلحها قبل الإصدار التالي.
ثم ضع هذا في المكان:
billing.plan_name.pro أو auth.error.invalid_password. احتفظ بنفس المفاتيح في كل مكان.t("key") في المكونات. في Flutter، استخدم غلاف التوطين واستدعِ نفس بحث المفتاح في الودجات. الهدف هو نفس المفاتيح، ليس نفس المكتبة.if (count === 1) مبعثرة عبر الشاشات.أخيرًا، اختبر لغة واحدة ذات كلمات أطول (الألمانية مثال كلاسيكي) ولغة ذات ترقيم مختلف. هذا يكشف بسرعة الأزرار التي تفيض، العناوين التي تنكسر بشكل سيئ، والتخطيطات التي تفترض طول كلمات إنجليزية.
إذا حافظت على الترجمات في مجلد مشترك (أو حزمة مُولدة) وتعاملت مع تغييرات النص كتغييرات كود، ستبقى تطبيقات الويب والهاتف متسقة حتى عندما تُبنى الميزات بسرعة في الدردشة.
نصوص واجهة المستخدم المترجمة هي نصف المشكلة فقط. معظم التطبيقات تعرض أيضًا قيمًا متغيرة مثل التواريخ، الأسعار، العدادات، والأسماء. إذا عاملت تلك القيم كنص عادي، ستحصل على تنسيقات غريبة، مناطق زمنية خاطئة، وجمل تبدو "غريبة" في العديد من اللغات.
ابدأ بتنسيق الأرقام والعملات والتواريخ بقواعد اللغة المحلية، لا بشيفرة مخصصة. مستخدم في فرنسا يتوقع "1 234,50 €"، بينما مستخدم في الولايات المتحدة يتوقع "$1,234.50". نفس الشيء بالنسبة للتواريخ: "03/04/2026" غامض، لكن التنسيق المحلي يوضّح.
الفخ التالي هو المناطق الزمنية. يجب أن يخزن الخادم الطوابع الزمنية بصورة محايدة (عادة UTC)، لكن المستخدمين يتوقعون رؤية الأوقات في منطقتهم الزمنية. مثال: أمر أُنشيء الساعة 23:30 UTC قد يكون "غدًا" لشخص في طوكيو. قرّر قاعدة واحدة لكل شاشة: عرض وقت محلي للمستخدم للأحداث الشخصية، وعرض منطقة زمنية ثابتة للأمور التجارية (مثل نوافذ استلام المتجر) ووسمها بوضوح.
تجنّب بناء جمل عن طريق ربط مقاطع مترجمة. هذا يكسر القواعد لأن ترتيب الكلمات يتغير حسب اللغة. بدلاً من:
"{count} " + t("items") + " " + t("in_cart")
استخدم رسالة واحدة تحتوي على مواضع: "{count} items in your cart". المترجم يمكنه إعادة ترتيب الكلمات بأمان.
RTL ليست مجرد اتجاه نصي. تدفق التخطيط ينقلب، بعض الأيقونات تحتاج انعكاسًا (مثل أسهم الرجوع)، والمحتوى المختلط (العربية مع رمز منتج إنجليزي) قد يظهر بترتيب مفاجئ. اختبر شاشات حقيقية، وليس مجرد تسمية واحدة، وتأكد أن مكونات الواجهة تدعم تغيير الاتجاه.
لا تترجم ما كتبه المستخدم (الأسماء، العناوين، تذاكر الدعم، رسائل الدردشة). يمكنك ترجمة الملصقات المحيطة، ويمكنك تنسيق البيانات الوصفية المحيطة (تواريخ، أرقام)، لكن المحتوى نفسه يجب أن يبقى كما هو. إذا أضفت الترجمة الآلية لاحقًا، اجعلها ميزة صريحة مع مفتاح تبديل "الأصل/المترجم".
مثال عملي: قد يظهر تطبيق مبني بـ Koder.ai "{name} renewed on {date} for {amount}". احتفظ بها كرسالة واحدة، نسّق {date} و{amount} بحسب locale، واعرضها في المنطقة الزمنية للمستخدم. هذا النمط الواحد يمنع الكثير من دين الترجمة.
قواعد سريعة تمنع الأخطاء عادةً:
دين الترجمة عادةً يبدأ كـ "نص واحد سريع" ويتحول إلى أسابيع من التنظيف لاحقًا. في المشاريع المبنية بالدردشة، يمكن أن يحدث ذلك أسرع لأن نص واجهة المستخدم يُولد داخل المكونات والنماذج وحتى رسائل الخلفية.
القضايا الأكثر تكلفة هي التي تنتشر عبر التطبيق وتصبح صعبة الاكتشاف.
تخيّل تطبيق React على الويب وFlutter على الجوال يعرضان لافتة فواتير: "You have 1 free credit left". أحدهم يغيّر نص الويب إلى "You have one credit remaining" ويبقي المفتاح كالجملة الكاملة. يبقى الجوال بالمفتاح القديم. الآن لديك مفتاحان لمفهوم واحد، والمترجمون يرون كلاهما.
نمط أفضل هو مفاتيح ثابتة (مثل billing.creditsRemaining) والجمع باستخدام رسائل ICU حتى يكون النحو صحيحًا عبر اللغات. إذا كنت تستخدم أداة توليد مثل Koder.ai، أضف قاعدة مبكرة: أي نص موجه للمستخدم ناتج في الدردشة يجب أن يهبط في ملفات الترجمة، لا داخل المكونات أو أخطاء الخادم. هذه العادة الصغيرة تحمي معمارية التدويل بينما يكبر المشروع.
عندما يشعر التدويل بالفوضى، عادة السبب أن الأساسيات لم تُكتب. قائمة فحص صغيرة ومثال واحد ملموس يحافظان على فريقك (وأنت المستقبلي) خارج دين الترجمة.
إليك قائمة سريعة يمكنك تشغيلها على كل شاشة جديدة:
billing.invoice.paidStatus، لا billing.greenLabel).مثال بسيط: تطلق شاشة فواتير بالإنجليزية والإسبانية واليابانية. الواجهة فيها: "Invoice", "Paid", "Due in 3 days", "1 payment method" / "2 payment methods", وإجمالي مثل "$1,234.50". إذا بنيت هذا بمعمارية تدويل للتطبيقات المبنية بالمحادثة، تعرف المفاتيح مرة واحدة (مشتركة بين الويب والجوال)، وكل لغة تملأ القيم. "Due in {days} days" تصبح رسالة ICU، وتنسيق المال يأتي من مُنسّق محلي، ليس من فاصلات ثابتة.
نزّل دعم اللغة ميزة بميزة، لا كإعادة كتابة كبيرة:
وثّق شيئين حتى تبقى الميزات الجديدة متسقة: قواعد تسمية المفاتيح (بما في ذلك أمثلة) و"تعريف الإنجاز" للنصوص (لا نصوص مضمّنة، ICU للجمع، تنسيق للتواريخ/الأرقام، إضافتها إلى الكتالوج المشترك).
الخطوات التالية: إذا كنت تبني في Koder.ai، استخدم Planning Mode لتعريف الشاشات والمفاتيح قبل توليد الواجهة. ثم استخدم لقطات rollback للتكرار الآمن على النص والترجمات عبر الويب والهاتف دون المخاطرة بإصدارٍ معطّل.