استخدام msgctxt: إضافة سياق إلى ترجمات Gettext

تُترجم الكلمة الإنجليزية "Book" إلى الألمانية، ويعيد المترجم بثقة "Buch". بعد ثلاثة أسابيع، يصل تقرير خطأ: زر "Book a table" في نموذج الحجز الخاص بك أصبح الآن يُقرأ "Buch a table" - الاسم، وليس الفعل. الكلمة كانت صحيحة. لكن السياق كان مفقودًا. هذا هو أحد أكثر الإخفاقات الصامتة شيوعًا في توطين البرمجيات، وقد وُجد الحل في Gettext منذ عقود: msgctxt.
إذا سبق لك أن نشرت ترجمة كانت فيها سلسلة إنجليزية واحدة صحيحة في مكان وغير منطقية في مكان آخر، فقد واجهت مشكلة الغموض. تتطابق الكلمة المصدر نفسها مع ترجمات مختلفة اعتمادًا على مكان ظهورها. بدون msgctxt، ليس لدى المترجم (بشريًا كان أم ذكاءً اصطناعيًا) طريقة لتمييز هذه الحالات، لأن كل ما يراه هو السلسلة المجردة msgid "Book". يشرح هذا الدليل كيفية عمل msgctxt في Gettext، وكيفية ترميز كود المصدر الخاص بك باستخدام _x() و _ex()، وكيف يظهر في ملفات .po الخاصة بك، ولماذا يعتبر مسار الترجمة الواعي بالسياق هو الطريقة الوحيدة الموثوقة للحصول على هذه السلاسل بشكل صحيح على نطاق واسع.
مشكلة الغموض: كلمة واحدة، معانٍ متعددة
اللغة الطبيعية مليئة بالكلمات التي تنهار إلى رمز إنجليزي واحد ولكنها تنقسم إلى كلمات متعددة في لغات أخرى. يرى المترجم فقط السلسلة المصدر، لذا عندما يتشارك عنصران من واجهة المستخدم غير مرتبطين في نفس الكلمة الإنجليزية، فإنهما يتشاركان نفس msgid - وتقوم Gettext بدمجهما في إدخال واحد.
كلمات تنقسم عبر اللغات
لنتأمل ثلاثة أمثلة كلاسيكية:
- "Book" - الاسم (كتاب على رف) مقابل الفعل ("Book a flight" - احجز رحلة). الألمانية: Buch مقابل buchen.
- "Post" - نشر محتوى مقابل إرسال بريد. الفرنسية: publier مقابل courrier.
- "Order" - ترتيب/تسلسل مقابل عملية شراء. الإسبانية: orden مقابل pedido.
في اللغة الإنجليزية، يستخدم الكود الخاص بك نفس السلسلة الحرفية في كلا المكانين:
// Somewhere in a library catalog screen
echo __( 'Book', 'mytextdomain' );
// Somewhere in a reservation form button
echo __( 'Book', 'mytextdomain' );
كيف تنهار السلاسل المتطابقة في إدخال واحد
عند تشغيل wp i18n make-pot أو xgettext، ينهار كلا الاستدعاءين في إدخال .po واحد:
#: catalog.php:42 reservation-form.php:88
msgid "Book"
msgstr ""
يوجد msgstr واحد فقط لملئه. مهما اختار المترجم، ستكون إحدى الشاشتين خاطئة. لا يمكن للمترجم إصلاح هذا حتى لو فهم المشكلة، لأن التنسيق نفسه لا يسمح له بتقديم ترجمتين لـ msgid واحد. الغموض جزء لا يتجزأ من البيانات.
ما هو msgctxt وكيف يزيل الغموض؟
msgctxt هو سلسلة سياق مرفقة بإدخال الترجمة. باختصار: يضيف مفتاحًا ثانيًا بجانب msgid، لذا يتعامل Gettext مع (context, msgid) كزوج فريد. يصبح إدخالان لهما نفس msgid ولكن msgctxt مختلف سلسلتين منفصلتين قابلتين للترجمة بشكل مستقل.
في نموذج البحث الخاص بـ Gettext، عادةً ما يتم تحديد الترجمة بواسطة السلسلة المصدر فقط. مع msgctxt، يصبح المفتاح هو مزيج السياق بالإضافة إلى المصدر. هذه هي الآلية بأكملها، ولهذا السبب تعمل بسلاسة: أنت لا تُغير النص المعروض، بل مفتاح البحث الداخلي فقط.
سطر msgctxt في ملف .po
في ملف .po، يظهر السياق في سطر خاص به مباشرة فوق msgid:
#: catalog.php:42
msgctxt "noun"
msgid "Book"
msgstr "Buch"
#: reservation-form.php:88
msgctxt "verb"
msgid "Book"
msgstr "Reservieren"
الآن هناك إدخالان. لهما قيم msgid متطابقة ولكن قيم msgctxt مميزة، لذا يحصل كل منهما على msgstr خاص به. يعرض الكتالوج Buch؛ ويعرض زر الحجز Reservieren. يختار وقت التشغيل الصحيح لأن الكود الخاص بك أخبره بالسياق الذي يجب استخدامه.
قارن ذلك بالإصدار المعطل ذي الإدخال الواحد أعلاه. الفرق ليس في جودة الترجمة - بل في ما إذا كان نموذج البيانات يسمح بوجود الإجابة الصحيحة من الأساس.
ترميز الكود الخاص بك: _x() و _ex()
لإصدار msgctxt في قالب .po الخاص بك، تستدعي دالة ترجمة واعية بالسياق بدلاً من الدالة العادية. في WordPress، هذه هي _x() وأختها التي تصدرها _ex()؛ في Gettext الخام، ترتبط بـ pgettext().
الاختيار بين _x() و _ex()
تأخذ دالة __() العادية السلسلة ونطاق النص. بينما تقوم النسخة الواعية بالسياق بإدراج السياق كحجة ثانية:
// Without context - ambiguous
__( 'Book', 'mytextdomain' );
// With context - the second argument is the msgctxt
_x( 'Book', 'noun', 'mytextdomain' ); // returns the translated string
_ex( 'Book', 'verb', 'mytextdomain' ); // echoes it directly
// Real-world disambiguation
_x( 'Post', 'verb: publish content', 'mytextdomain' );
_x( 'Post', 'noun: mail item', 'mytextdomain' );
_x( 'Order', 'sequence or sorting', 'mytextdomain' );
_x( 'Order', 'customer purchase', 'mytextdomain' );
الحجة الثانية هي تسمية السياق. لا تُعرض أبدًا للمستخدمين - وهي موجودة فقط لإزالة الغموض، سواء لبحث Gettext أو لمن (أو ما) يقوم بالترجمة. اكتب السياقات كتلميحات قصيرة وواضحة: 'noun', 'verb', 'button label', 'admin menu'. سياق غامض مثل '1' صحيح من الناحية الفنية ولكنه عديم الفائدة للمترجم.
عند إعادة إنشاء القالب، يتعرف المستخرج على هذه الاستدعاءات ويصدر سطر msgctxt. تقوم أدوات مثل Poedit ومحررات PO الأخرى بتجميع الإدخالات بشكل مرئي حسب السياق، بحيث يرى المترجم البشري على الفور أن "Book (اسم)" و "Book (فعل)" مهمتان مختلفتان. إذا كنت لا تزال تقوم بإعداد سلسلة أدوات الاستخراج الخاصة بك أو تواجه صعوبة في كيفية تفاعل المتغيرات مع هذه السلاسل، فإن دليلنا حول ترجمة ملفات PO دون كسر متغيرات الكود يغطي سير العمل المحيط بالتفصيل.
لماذا يخطئ المترجمون الساذجون - والعديد من أدوات الذكاء الاصطناعي - في هذا
هذا هو الجزء غير المريح. إضافة msgctxt إلى مصدرك ضروري ولكنه ليس كافيًا. يساعد السياق فقط إذا كان الشيء الذي يقوم بالترجمة يقرأه بالفعل.
وضع الفشل الناتج عن تجاهل السياق
يقوم برنامج نصي للترجمة المجمعة الساذجة بهذا: فهو يتكرر عبر الإدخالات، ويأخذ كل msgid، ويرسله إلى واجهة برمجة تطبيقات الترجمة، ويكتب النتيجة إلى msgstr. لا ينظر أبدًا إلى سطر msgctxt. لذا، حتى لو كتبت بعناية _x( 'Book', 'noun' ) و _x( 'Book', 'verb' )، فإن البرنامج النصي يرسل طلبين متطابقين - "ترجمة Book" - ويلصق نفس الإجابة في كليهما. يتم تجاهل كل جهد الترميز الخاص بك في الخطوة الأخيرة.
العديد من أدوات الترجمة بالذكاء الاصطناعي ذات الأغراض العامة لديها نفس النقطة العمياء. إنها مصممة لترجمة النص، و msgctxt هي بيانات وصفية وليست نصًا. إذا قامت الأداة بتسوية ملف .po الخاص بك إلى قائمة من السلاسل قبل إرسالها إلى النموذج، فلن يصل السياق أبدًا إلى توجيه النموذج. يعود النموذج، لافتقاره لأي إشارة، إلى المعنى الأكثر شيوعًا إحصائيًا للكلمة - عادةً الاسم لـ "Book"، ومعنى النشر لـ "Post" - ويترجم الحالة الأقل شيوعًا بشكل خاطئ وصامت. لن ترى خطأ. سترى تقرير خطأ من مستخدم ألماني بعد أسابيع.
السياق والجمع يشتركان في نفس السبب الجذري
هذا هو أيضًا المكان الذي يتقاطع فيه التعامل مع الجمع والسياق، لأن كلاهما يعتمد على فهم الأداة لهيكل Gettext بدلاً من التعامل مع الملف كنص مسطح. إذا كانت سلاسلك تتضمن أيضًا أشكالًا تعتمد على العدد، فإن نفس الوعي الهيكلي مهم - نحن نحلل ذلك في فهم صيغ الجمع في Gettext.
كيف يستخدم مسار الترجمة الواعي بالسياق msgctxt
مسار الترجمة الذي يحترم msgctxt يفعل عكس البرنامج النصي الساذج. يقوم بتحليل ملف .po كبيانات منظمة، ويحتفظ بسياق كل إدخال مرفقًا بسلسلته المصدر، ويمرر هذا السياق إلى الذكاء الاصطناعي كجزء من التوجيه - بحيث يعرف النموذج أنه يترجم "Book" تحديدًا كفعل، وليس بشكل مجرد.
السياق كإشارة من الدرجة الأولى
هذه هي الطريقة التي يتعامل بها SimplePoTranslate مع المشكلة. يقرأ الذكاء الاصطناعي الواعي بالسياق الخاص به سطر msgctxt كإشارة من الدرجة الأولى: عندما يتشارك إدخالان في msgid ولكنهما يختلفان في السياق، يتم ترجمتهما كسلاسل مميزة كما هي في الواقع، ويُعلم تلميح السياق النموذج باختيار الكلمة. النتيجة هي أن "Book (اسم)" تعود كـ Buch بينما "Book (فعل)" تعود كـ buchen أو reservieren - تلقائيًا، دون أن تحتاج إلى تصحيح يدوي لكل مصطلح غامض.
على نفس القدر من الأهمية، يحافظ مسار العمل على سطر msgctxt في المخرجات. قد تقوم أداة أضعف بإزالة السياق أثناء عملية التكرار، مما يؤدي إلى دمج إدخاليك مرة أخرى في إدخال واحد وإعادة تقديم الغموض في الدمج التالي. دعم Gettext الكامل يعني أن السياق يبقى سليمًا خلال الترجمة، وتجميع ملف .mo، وأي عمليات استيراد لاحقة - لذا فإن إزالة الغموض الخاص بك متينة، وليست تصحيحًا يدويًا لمرة واحدة. بالاشتراك مع تأمين بناء الجملة (Syntax Locking) للعناصر النائبة داخل تلك السلاسل، تحصل على ترجمات صحيحة سياقيًا وسليمة هيكليًا.
لا يزال قرار الترميز يخصك - أنت وحدك تعرف أن كلمة "Book" معينة هي فعل. ولكن بمجرد أن تقوم بتعليق المصدر الخاص بك باستخدام _x() و _ex()، يحول مسار العمل الواعي بالسياق هذا التعليق إلى ترجمات صحيحة عبر كل لغة مستهدفة دون الحاجة إلى الإشراف على كل سلسلة على حدة.
الخلاصة
مشكلة الغموض - كلمة إنجليزية واحدة، معانٍ متعددة - ليست خاصية يمكنك تجاهلها؛ إنها ميزة هيكلية لكيفية تخزين Gettext للسلاسل. الحل هو msgctxt: علّق السلاسل الغامضة في مصدرك باستخدام _x() و _ex()، امنح كل ظهور تسمية سياق واضحة، ودع Gettext يربط الترجمة بزوج (context, msgid).