استكشاف الأخطاء وإصلاحها
كل إدخال هنا يمثل حالة فشل واجهها أحدهم فعلاً. يذكر كل إدخال العَرَض وسبب حدوثه والحل، ويعود معظمها إلى أحد ثلاثة أسباب: العبارة غير موجودة في المشروع، أو لم تتم تهيئة حزمة SDK مطلقاً، أو تمت كتابة عنصر نائب بصيغة لا تناسب السياق.
لا شيء يُترجَم — تبقى كل العبارات بلغتها المصدر
عندما يظهر النص المصدر في كل شيء، فالأرجح أن الحزمة SDK فشلت في التهيئة. وهي تفشل بهدوء بحكم التصميم (إذ يجب ألا تتعطل واجهتك أبدًا)، لذا فإن الإشارة الواضحة الوحيدة هي وحدة تحكم المتصفح — ابدأ بفحصها. وفيما يلي الأسباب المعتادة، مرتبة حسب الاحتمال:
- CORS. أول طلب يرسله SDK هو
authorize-project؛ فإذا لم تُدرج واجهة API الخاصة بك أصل التطبيق ضمن القائمة المسموح بها، يحظره المتصفح وتتوقف التهيئة عند هذه النقطة. تذكّر أن الأصل (origin) هو المخطط + المضيف + المنفذ بالضبط، فـlocalhost:5173وlocalhost:5174أصلان مختلفان، وتتغير منافذ خوادم التطوير عندما يكون المنفذ المعتاد مشغولًا. أضف كل منفذ تطوّر عليه فعليًا إلى القائمة المسموح بها. - بيانات اعتماد خاطئة. فقدان معرّف المشروع أو مفتاح API أو كتابته بشكل خاطئ يُفشل استدعاء التفويض نفسه. تعرض وحدة التحكم الطلب الفاشل وعنوان URL.
- أنت تشاهد اللغة الأساسية. في اللغة الأساسية تكون «الترجمة» هي النص المصدر نفسه — فلا يتغير شيء بصورة ظاهرة. بدّل إلى لغة مستهدفة لإجراء الاختبار.
# The SDK's first call is an authorize-project request.
# If the browser blocks it, NOTHING translates. Check the console for:
Access to fetch at 'https://your-api/api/authorize-project/...'
from origin 'http://localhost:5173' has been blocked by CORS policy
# Fix: allowlist the app's EXACT origin (scheme + host + port) on the API.
CORS_ALLOWED_ORIGINS=http://localhost:5173,https://app.example.com عبارة واحدة لا تُترجم (بينما تُترجم الأخرى)
العبارة في شيفرتك هي مفتاح البحث — العبارة هي المفتاح. إذا ظهرت عبارة واحدة بنصها المصدر بينما تُترجم العبارات المجاورة لها، فهذا المفتاح غير موجود في فهرس المشروع:
- لم تُسجَّل قط. يتم التسجيل عبر اكتشاف الرموز، ولا يستطيع إجراءه سوى مفتاح الكتابة. إذا نشأت العبارة بينما كان التطبيق يعمل بمفتاح للقراءة فقط، فلم تُرسل إلى أي مكان — شغّل الشاشة مرة واحدة في بيئة التطوير بمفتاح كتابة، أو أضف العبارة من لوحة التحكم.
- تغيّر النص. أي تعديل، سواء كلمة أو علامة ترقيم أو حتى مسافة بيضاء، يجعله عبارة جديدة بلا ترجمات بعد. أما الترجمات القديمة فتبقى موجودة تحت المفتاح القديم. ويظهر هذا خصوصًا في كتل المحتوى: فكتلة
<Translate>تُجزَّأ إلى رموز من الترميز الخاص بها، لذا فتعديل نص الكتلة أو بنيتها يغيّر ما يُبحث عنه، وتعود الكتلة بهدوء إلى النص المصدر إلى أن يُسجَّل الشكل الجديد ويُترجم. - الترجمة الآلية لم تواكب بعد. قد تتأخر ترجمات العبارة المسجلة حديثًا للحظات، ولا تحصل اللغة المحلية غير الواردة في قائمة اللغات المستهدفة للمشروع على ترجمة إطلاقًا. تحقق من العبارة في لوحة التحكم: هل هي مسجلة؟ وهل تُرجمت إلى هذه اللغة المحلية؟
لماذا لا تُحدث العبارات المفقودة خطأً؟
هذا مقصود. ففي بيئة الإنتاج يجب أن يتراجع نظام الترجمة إلى نص مصدر مقروء، لا إلى صفحة معطلة، لذلك تُعرض العبارة غير المسجلة بنصها المصدر ويبقى الـ SDK صامتًا. وفي المقابل: عند استخدام مفتاح القراءة فقط تكون العبارة المفقودة صامتة؛ فلا يُسجَّل شيء ولا تُسجَّل أي عبارة، ولا تعلم بالأمر إلا حين يرى مستخدم نصًا بالإنجليزية. والانضباط الذي يمنع ذلك هو فصل المفاتيح: طوّر باستخدام مفتاح كتابة ليتم اكتشاف كل عبارة تعرضها واجهتك، ثم انشر بمفتاح للقراءة فقط.
# Development — write key: new phrases are discovered,
# registered, and machine-translated as you build.
VITE_LANGSYS_API_KEY=write-key
# Production — read-only key: fetches the finished catalog only.
# Unregistered phrases silently render their source text.
VITE_LANGSYS_API_KEY=read-only-key يظهر العنصر النائب كما هو حرفيًا — "Hi %name%" أو "Hi {name}"
- في استدعاءات
t()، تكون العناصر النائبة بقوس مفرد{name}، ولا يعمل الاستيفاء إلا عند تمرير المعاملات:t('Hello, {name}!', 'Greetings', { name }). - في ترميز
<Translate>/<Phrase>ضمن React أو Vue أو Svelte، اكتب العناصر النائبة بالصيغة%name%. يعالج مُصرِّف الإطار الصيغة المجردة{name}بوصفها تعبيرًا برمجيًا قبل أن يراها SDK؛ أما%name%فتبقى سليمة بعد التصريف، ويعيد SDK تحويلها إلى{name}عند الالتقاط. ولا بأس باستخدام{name}العادية في HTML الخالصة، إذ لا توجد فيها صيغة قوالب قد تتعارض معها. - المعاملات مفقودة؟ إذا لم يتم تمرير أي
params، يُعرض القالب كما هو — وهذا هو%name%الحرفي الذي تراه.
يعمل ثم يتعطل بعد أن أبدّل اللغات ذهابًا وإيابًا
حدّث SDK. هذه أخطاء حقيقية جرى إصلاحها في هذا المجال: العودة إلى اللغة الأولية/لغة SSR كانت تُبقي الكتالوج السابق نشطًا (أُصلح في langsys-js-typescript الإصدار 0.2.2)، وكان مستهلكو Translate/Phrase في كتل المحتوى قد يعلقون إلى الأبد على كتالوج مُحمَّل من الخادم (أُصلح في 0.2.3). إذا ظهرت لك نصوص بلغة قديمة أو كتل لا يكتمل تحميلها أبدًا، فقارن إصدارك المثبّت بسجل التغييرات الخاص بـ SDK الأساسية قبل تصحيح الأخطاء في شيفرتك.
من جهة الخادم: تعرض الصفحة الخطأ 500 بدلًا من التراجع السلس
يؤدي عميل حزمة PHP SDK إلى رمي استثناء عند إنشائه دون بيانات اعتماد، ولذلك يفشل تطبيق Laravel (أو PHP العادي) الذي تكون قيمة LANGSYS_API_KEY فيه فارغة عند حل الحاوية، قبل تشغيل أي ترجمة. عيّن بيانات الاعتماد (أو اربط عميلًا بديلًا إذا كان على التطبيق أن يعمل بدونها، كما تفعل التطبيقات التجريبية). وبعد توفر بيانات الاعتماد، فإن أعطال واجهة API أثناء التشغيل تُعالَج بسلاسة: فإذا تعذّر الوصول إلى الواجهة أو رفضت الطلب، يُعرَض النص المصدر ويُبلَّغ عن الاستثناء، ولا تتعطل الصفحة أبدًا.
من جهة الخادم: يتعلّق الطلب الأول ثم يصبح كل شيء سريعًا
مهلة HTTP في PHP SDK هي 30 ثانية، وقد تقترب منها نسخة API تستيقظ من حالة الخمول عند الاستدعاء الأول تمامًا. وينتهي ذلك الطلب إما ببطء (فيُسخّن ذاكرة التخزين المؤقت للكتالوج — وكل ما يليه يُقدَّم من الذاكرة المؤقتة) أو بانتهاء المهلة وعرض النص المصدر. إذا كان أول تحميل لصفحتك بعد فترة هدوء بطيئًا، فهذا هو السبب؛ فهو ناتج عن تسخين API وليس عن مشكلة في تكاملك.
من جهة الخادم: عرضتُ الصفحة بمفتاح كتابة لكن العبارة لا تظهر في لوحة التحكم
تُوضَع العبارات المكتشفة في قائمة انتظار أثناء الطلب، ثم تُرسَل بعد إرسال الاستجابة — وهذا هو سلوك الوسيط القابل للإنهاء (terminable middleware) في Laravel. حدّث لوحة التحكم بعد لحظات من تحميل الصفحة، وليس أثناءه. في بيئات التشغيل الطويلة الأمد للعمّال (Octane وFrankenPHP وRoadRunner) تأكد من استخدام التكامل المرفق مع حزمة SDK، إذ إن خطّاف الإيقاف العادي في PHP لا يُنفَّذ أبدًا بين طلبات العامل.
العرض على الخادم: تتسرّب الترجمات من طلب إلى آخر
تحتفظ حزمة JS الأساسية بـكتالوج لغة واحدة فقط في كل مرة، فهي كائن وحيد (singleton) على مستوى الوحدة. وأي خادم Node يعرض لغات مختلفة لطلبات متزامنة ستتنازع الطلبات فيه على الحالة نفسها. اجلب الكتالوج لكل طلب وهيّئ العميل منه، وهو النمط المتبع في أدلة Next.js وNuxt وSvelteKit، أو شغّل لغة واحدة لكل عملية.
ما زالت المشكلة قائمة؟ افتح بلاغاً (issue) في مستودع الـ SDK الخاص بك — typescript أو react أو vue أو svelte أو php أو laravel — مع مخرجات وحدة التحكم (أو سجل الخادم) للطلب الفاشل. أو راسلنا على [email protected].