دليل عملي لكتابة تعبيرات CEL في نظام إدارة المحتوى.
دليل عملي لكتابة تعبيرات CEL في نظام إدارة المحتوى.
CEL (لغة التعبير المشتركة) هي لغة برمجة نصية خفيفة مدمجة في نظام إدارة المحتوى لدينا. تتيح لك كتابة تعبيرات ديناميكية يمكنها سحب البيانات من المستندات، وقراءة معلمات عنوان URL، وحساب القيم أثناء التنفيذ.
إليك ما يحدث عند تشغيل نص CEL:
برنامجك النصي المحرك النتيجة
| | |
v v v
documents.get("article", "intro") --> يجلب من قاعدة البيانات --> { headline: "مرحبًا", body: "..." }
.headline --> يستخرج الحقل --> "مرحبًا"
اعتبر CEL لغة استعلام للقراءة فقط. لا يمكنها تعديل أي شيء في قاعدة البيانات؛ فهي تقرأ البيانات وتعيد نتيجة محسوبة فحسب. وهذا يجعل استخدامها آمنًا في أي مكان داخل نظام إدارة المحتوى.
كل تعبير CEL يمكنه الوصول إلى ثلاثة عناصر:
| الكائن | ماهيته | مثال |
|---|---|---|
documents | جلب أي مستند من نظام إدارة المحتوى | documents.get("country", "us") |
meta | معلومات عن الطلب الحالي (الإعداد اللغوي، معلمات URL) | meta.locale, meta.params.slug |
schema | تعريفات حقول المستند الحالي | schema.fields |
أقوى ميزة في CEL هي إمكانية جلب المستندات من أي مكان داخل نظام إدارة المحتوى لديك.
الصياغة: documents.get(schemaName, identifier)
لنفترض أن لديك مستند article مخزنًا بالمعرِّف "welcome-post":
// مخزن في نظام إدارة المحتوى كالتالي: article / welcome-post
{
"headline": "مرحبًا بكم في منصتنا",
"author": "سارة تشين",
"body": "نحن متحمسون للإعلان...",
"tags": ["إعلان", "أخبار"]
}
لجلب المستند بالكامل:
documents.get("article", "welcome-post")
يعيد:
{
"headline": "مرحبًا بكم في منصتنا",
"author": "سارة تشين",
"body": "نحن متحمسون للإعلان...",
"tags": ["إعلان", "أخبار"]
}
لجلب العنوان فقط:
documents.get("article", "welcome-post").headline
يعيد: "مرحبًا بكم في منصتنا"
لجلب اسم المؤلف:
documents.get("article", "welcome-post").author
يعيد: "سارة تشين"
عندما تحتوي صفحتك على مسارات ديناميكية (مثل /articles/[slug])، يمكنك استخدام meta.params للحصول على معلمة عنوان URL وجلب المستند الصحيح.
إذا زار أحدهم /articles/welcome-post:
documents.get("article", meta.params.slug).headline
يعيد: "مرحبًا بكم في منصتنا"
هكذا تبني صفحات ديناميكية؛ إذ يعمل نص CEL نفسه مع أي مقال، مستخدمًا أي قيمة slug موجودة في عنوان URL.
الصياغة: documents.find(schemaName) أو documents.find(schemaName, filter)
// جلب جميع البلدان
documents.find("country")
يعيد:
[
{ "code": "us", "name": "الولايات المتحدة", "flag": "US" },
{ "code": "sa", "name": "المملكة العربية السعودية", "flag": "SA" },
{ "code": "gb", "name": "المملكة المتحدة", "flag": "GB" }
// جلب البلدان باستخدام عامل تصفية
documents.find("country", { "where": { "code": "us" } })
يعيد:
[
{ "code": "us", "name": "الولايات المتحدة", "flag": "US" }
]
لديك hero-block يجب أن يعرض عنوانًا مأخوذًا من مستند article.
مستند المقال الخاص بك (المعرِّف: "homepage-hero"):
{
"headline": "ابنِ أسرع، وانشر بذكاء أكبر",
"subheadline": "نظام إدارة المحتوى الحديث للمطورين"
}
نص CEL في حقل عنوان كتلة البطل:
documents.get("article", "homepage-hero").headline
النتيجة: تعرض كتلة البطل "ابنِ أسرع، وانشر بذكاء أكبر"
تبني صفحة على /countries/[code] وتريد عرض الاسم الكامل للبلد.
مستندات البلد الخاصة بك:
// country / us
{ "code": "us", "name": "الولايات المتحدة", "flag": "US", "languages": ["en", "es"] }
// country / sa
{ "code": "sa", "name": "المملكة العربية السعودية", "flag": "SA", "languages": ["ar",
نص CEL:
documents.get("country", meta.params.code).name
عندما يزور أحدهم /countries/us:
meta.params.code = "us"عندما يزور أحدهم /countries/sa:
meta.params.code = "sa"اعرض عناوين مختلفة بناءً على إعداد المستخدم اللغوي.
meta.locale == "ar-SA" ? "مرحبًا بكم" : "أهلًا"
إذا كان الإعداد اللغوي "ar-SA": يعيد "مرحبًا بكم"
إذا كان أي شيء آخر: يعيد "أهلًا"
يحتوي مستند article لديك على حقل countryCode، وتريد الحصول على الاسم الكامل للبلد.
مستند المقال:
{ "headline": "أخبار من الولايات المتحدة", "countryCode": "us" }
نص CEL:
documents.get("country", documents.get("article", "us-news").countryCode).name
ما يحدث:
documents.get("article", "us-news") يعيد { "headline": "أخبار من الولايات المتحدة", "countryCode": "us" }.countryCode تستخرج "us"documents.get("country", "us") يعيد { "code": "us", "name": "الولايات المتحدة", ... }.name تستخرج "الولايات المتحدة"النتيجة: "الولايات المتحدة"
إذا كان من الممكن ألا يوجد مستند، يمكنك توفير قيمة احتياطية:
documents.get("article", meta.params.slug) != null
? documents.get("article", meta.params.slug).headline
: "المقال غير موجود"
أو تحقق مما إذا كان حقل محدد موجودًا:
documents.get("article", "intro").author != null
? documents.get("article", "intro").author
: "مؤلف غير معروف"
مقالك يحتوي على وسوم، وتريد التحقق مما إذا كان وسم محدد موجودًا:
"مميز" in documents.get("article", "welcome-post").tags
يعيد: true إذا كان المقال يحتوي على الوسم "مميز"
الحصول على أول وسم:
documents.get("article", "welcome-post").tags[0]
يعيد: "إعلان" (أول وسم)
عدّ الوسوم:
size(documents.get("article", "welcome-post").tags)
يعيد: 2 (عدد الوسوم)
المسارات البارامترية هي المفتاح لإنشاء صفحات ديناميكية ومحلية. عندما تعرّف نمط مسار مثل /{lang}/landingPage، يقوم نظام إدارة المحتوى باستخراج المعلمات من عنوان URL ويجعلها متاحة عبر meta.params.
تعريف نمط المسار:
تستخدم المسارات الصيغة :paramName أو {paramName} لتعريف المقاطع الديناميكية:
| النمط | عنوان URL مثال | المعلمات المستخرجة |
|---|---|---|
/:lang/landingPage | /ko/landingPage | { lang: "ko" } |
/{country}/{lang}/products | /us/en/products | { country: "us", lang: "en" } |
/articles/:slug | /articles/welcome-post | { slug: "welcome-post" } |
ربط المعلمات: يمكن ربط كل معلمة مسار بمخطط مستند لأغراض التحقق:
{
"pattern": "/{lang}/landingPage",
"param_bindings": {
"lang": "language"
}
}
يبين هذا الربط لنظام إدارة المحتوى ما يلي:
lang من عنوان URLlanguage (يبحث عن مستند يطابق فيه content.code)تهيئة المسار:
/{lang}/landingPage/{lang}/landingPage{ "lang": "language" }مستندات الترحيب الخاصة بك:
// greeting / ko
{ "code": "ko", "headline": "환영합니다", "subheadline": "우리 플랫폼에 오신 것을 환영합니다" }
// greeting / en
{ "code": "en", "headline": "Welcome", "subheadline": "Welcome to our platform" }
// greeting / ja
{ "code": "ja", "headline"
نص CEL لجلب المحتوى المحلي:
documents.get("greeting", meta.params.lang).headline
كيف يتم الحل:
| عنوان URL | meta.params.lang | النتيجة |
|---|---|---|
/ko/landingPage | "ko" | "환영합니다" |
/en/landingPage | "en" | "Welcome" |
/ja/landingPage | "ja" | "ようこそ" |
للمسارات مثل /{country}/{lang}/products:
تهيئة المسار:
{
"pattern": "/{country}/{lang}/products",
"param_bindings": {
"country": "country",
"lang": "language"
}
}
نصوص CEL:
// الحصول على اسم البلد
documents.get("country", meta.params.country).name
// الحصول على قائمة المنتجات المحلية بناءً على البلد
documents.find("product", { "where": { "country": meta.params.country } })
// مدمج: إظهار ترحيب خاص بالبلد بلغة المستخدم
documents.get("greeting", meta.params.lang).headline + " من " + documents.get("country", meta.params.country).name
سلسلة التحقق:
يتحقق نظام إدارة المحتوى من المعلمات بشكل هرمي. بالنسبة لمسارات /{country}/{lang}:
country مقابل مخطط countrylang مقابل مخطط languagelang داخل مصفوفة country.languages[] (تحقق هرمي)يوفر meta.segments مسار عنوان URL الخام كمصفوفة، وهو مفيد عندما تحتاج إلى الوصول وفقًا للموضع دون معلمات مسماة.
كيف يعمل:
| مسار URL | meta.segments |
|---|---|
/articles/tech/ai-news | ["articles", "tech", "ai-news"] |
/ko/landingPage | ["ko", "landingPage"] |
/us/en/products/featured | ["us", "en", "products", "featured"] |
/ | [] |
| حالة الاستخدام | النهج الأفضل |
|---|---|
| المعلمات المسماة من نمط المسار | meta.params.lang |
| الوصول بناءً على الموضع | meta.segments[0] |
| الحصول على عمق المسار | size(meta.segments) |
| التحقق مما إذا كان المسار يحتوي على مقطع معين | "admin" in meta.segments |
// الحصول على المقطع الأول (غالبًا ما يكون رمز اللغة)
meta.segments[0]
// التحقق من عمق المسار
size(meta.segments) > 2 ? "عميق" : "سطحي"
// التحقق مما إذا كنا في قسم الإدارة
"admin" in meta.segments ? "وضع الإدارة" : "الوضع العام"
// احتياط: استخدام المقطع إذا لم يتم ربط المعلمة
has(meta.params.lang) ? meta.params.lang : meta.segments[0]
يحتوي كائن meta على كل السياق حول الطلب الحالي:
| الخاصية | النوع | الوصف | |
|---|---|---|---|
meta.locale | string | رمز الإعداد اللغوي الحالي (مثل "en-US", "ko-KR", "ar-SA") | |
meta.params | Record<string, string> | معلمات المسار المستخرجة من نمط عنوان URL | |
meta.segments | string[] | مسار عنوان URL المجزأ إلى مقاطع | |
meta.docId | string \| null | معرّف UUID للمستند الحالي (null للمستندات الجديدة) | |
meta.title | string | عنوان المستند الحالي |
يتبع رمز الإعداد اللغوي تنسيق BCP 47 (اللغة-المنطقة):
// التحقق من الإعدادات اللغوية للغات من اليمين إلى اليسار
meta.locale == "ar-SA" || meta.locale == "he-IL" ? "rtl" : "ltr"
// الحصول على جزء اللغة فقط
meta.locale.split("-")[0] // غير مدعوم - استخدم meta.params.lang بدلاً من ذلك
تكون معلمات المسار دائمًا سلاسل نصية. يتحقق نظام إدارة المحتوى منها مقابل المخططات المرتبطة قبل التنفيذ:
// الوصول إلى معلمة مسماة
meta.params.lang // "ko"
meta.params.country // "us"
meta.params.slug // "welcome-post"
// التحقق مما إذا كانت المعلمة موجودة
has(meta.params.category) // true/false
// الاستخدام في جلب المستندات
documents.get("greeting", meta.params.lang)
documents.ref("airports").get(meta.params.code)
مقاطع عنوان URL الخام كمصفوفة:
// الوصول عبر الفهرس (بدءًا من الصفر)
meta.segments[0] // المقطع الأول
meta.segments[1] // المقطع الثاني
// التحقق من الطول
size(meta.segments) // عدد المقاطع
// التحقق من وجود مقطع
"products" in meta.segments // هل يتضمن المسار "products"؟
معرّف UUID للمستند الحالي، مفيد للنصوص المرجعية الذاتية:
// متاح فقط عند تحرير مستندات موجودة
meta.docId != null ? "تعديل" : "إنشاء جديد"
// الاستخدام في منطق شرطي
meta.docId != null ? documents.get("article", meta.docId).status : "draft"
عنوان المستند الحالي:
// للاستخدام في العرض
"تحرير: " + meta.title
// شرط يعتمد على العنوان
meta.title.contains("Draft") ? "قيد العمل" : "منشور"
للحصول على صياغة أوضح عندما يكون المخطط معروفًا لكن المعرّف ديناميكيًا:
// النهج التقليدي
documents.get("airports", meta.params.code).name
// استخدام ref() - المخطط منفصل عن المعرّف الديناميكي
documents.ref("airports").get(meta.params.code).name
كلاهما متكافئ، لكن ref() يجعل الجزء الديناميكي أوضح.
documents.get("schema", "identifier") // جلب مستند واحد
documents.get("schema", "id").fieldName // جلب حقل محدد
documents.find("schema") // جلب جميع المستندات
documents.find("schema", { "where": {...}}) // استعلام مع عامل تصفية
documents.ref("schema").get(identifier) // عملية بحث متسلسلة
meta.locale // "en-US", "ar-SA", إلخ
meta.params.xyz // معلمة عنوان URL باسم "xyz"
meta.segments // مسار عنوان URL كمصفوفة: ["articles", "intro"]
meta.segments[0] // أول مقطع في المسار
meta.docId // معرّف المستند الحالي (أو null)
meta.title // عنوان المستند الحالي
// المقارنة
== != < <= > >=
// المنطق
&& || !
// الثلاثي (if-else)
condition ? valueIfTrue : valueIfFalse
// الانتماء
"value" in listOrMap
size(list) // عدد العناصر
size(string) // طول السلسلة
"text".startsWith("te") // true
"text".endsWith("xt") // true
"text".contains("ex") // true
has(object.property) // التحقق من وجود خاصية
إذا حدث خطأ ما، سترى أحد الرسائل التالية:
| الخطأ | ماذا يعني؟ |
|---|---|
SYNTAX_ERROR | خطأ كتابي في النص (علامة اقتباس مفقودة، معامل غير صحيح) |
TYPE_ERROR | تخلط بين أنواع لا تعمل معًا |
RUNTIME_ERROR | تم تشغيل النص لكنه واجه مشكلة (متغير غير معرّف) |
FETCH_LIMIT_EXCEEDED | تجلب عددًا كبيرًا جدًا من المستندات (الحد الأقصى 50) |
TIMEOUT | استغرق النص وقتًا طويلًا (الحد الأقصى 5 ثوانٍ) |
AST_DEPTH_EXCEEDED | التعبير متشعب بعمق كبير (العمق الأقصى: 50) |
SCRIPT_TOO_LONG | يتجاوز طول النص حد 5000 حرف |
صُمم محرك CEL ليكون قابلاً للتوسعة. تشمل القدرات المخطط لها مستقبلًا ما يلي:
// مستقبلًا: استدعاء خدمات خارجية عبر MCP
mcp.translate(meta.params.text, "en", meta.params.lang)
mcp.analyze(documents.get("article", meta.params.id).body)
// مستقبلًا: إنشاء محتوى مدعوم بالذكاء الاصطناعي
ai.summarize(documents.get("article", meta.params.id).body, 100)
ai.translate(meta.params.text, meta.params.targetLang)
ai.classify(meta.params.input, ["إيجابي", "سلبي", "محايد"])
ستتم إضافة هذه القدرات عبر نظام الدوال المسجل، مع الحفاظ على التوافق مع النصوص الحالية.
documents. أو meta. وسيعرض لك المحرر الخيارات المتاحةdocuments.get("schema", "id") أولًا، ثم أضف .fieldName!= null ? ... : ...documents.get() أو documents.find() يُحتسب ضمن حد الجلب البالغ 50 مرةhas(meta.params.category) قبل الوصول إليهايوضح هذا الدليل كيفية إنشاء صفحة هبوط متعددة اللغات يمكن الوصول إليها عبر /{lang}/landingPage.
في لوحة إدارة نظام إدارة المحتوى، أنشئ مخططًا مخصصًا باسم greeting:
{
"name": "greeting",
"fields": [
{ "name": "code", "type": "string", "required": true },
{ "name": "headline", "type": "string", "required": true },
{ "name": "subheadline"
أنشئ مستندات لكل لغة:
المستند: greeting / ko
{
"code": "ko",
"headline": "환영합니다",
"subheadline": "우리 플랫폼에 오신 것을 환영합니다",
"ctaText": "시작하기",
"ctaUrl": "/ko/get-started"
}
المستند: greeting / en
{
"code": "en",
"headline": "Welcome",
"subheadline": "Welcome to our platform",
"ctaText": "Get Started",
"ctaUrl": "/en/get-started"
}
المستند: greeting / ja
{
"code": "ja",
"headline": "ようこそ",
"subheadline": "私たちのプラットフォームへようこそ",
"ctaText": "始める",
"ctaUrl": "/ja/get-started"
}
أنشئ مسارًا بالإعدادات التالية:
/{lang}/landingPage/{lang}/landingPage {
"lang": "language"
}
أضف كتلة بطل إلى المسار بهذه النصوص لكل حقل:
حقل العنوان:
documents.get("greeting", meta.params.lang).headline
حقل العنوان الفرعي:
documents.get("greeting", meta.params.lang).subheadline
حقل نص الدعوة لاتخاذ إجراء:
documents.get("greeting", meta.params.lang).ctaText
حقل رابط الدعوة لاتخاذ إجراء:
documents.get("greeting", meta.params.lang).ctaUrl
أنشئ مسارًا يلتقط كل شيء في تطبيق Next.js الخاص بك:
// app/[...slug]/page.tsx
import { getCmsClient } from '@repo/renderer';
interface PageProps {
params: { slug: string[] };
}
export default async function Page({ params }: PageProps) {
زر عناوين URL التالية لمشاهدة المحتوى المحلي:
| عنوان URL | العنوان المتوقع |
|---|---|
/ko/landingPage | 환영합니다 |
/en/landingPage | Welcome |
/ja/landingPage | ようこそ |
عندما يزور المستخدم /ko/landingPage:
/{lang}/landingPagemeta.params.lang = "ko"languagedocuments.get("greeting", meta.params.lang) إلى محتوى اللغة الكوريةinterface CelMeta {
/** رمز الإعداد اللغوي الحالي (مثل 'en-US') */
locale: string;
/** معلمات المسار المستخرجة من عنوان URL */
params: Record<string, string>;
/** مقاطع مسار عنوان URL */
segments: string[];
/** معرّف المستند الحالي (إذا كان يتم تحرير مستند موجود) */
تعالج دالة extractParams مسارات عناوين URL:
النمط: /{country}/{lang}/products
المسار: /us/en/products
الخوارزمية:
1. توحيد كلاهما (إزالة الشرط المائل في النهاية)
2. التقسيم إلى مقاطع: ["us", "en", "products"] و["{country}", "{lang}", "products"]
// ربط بسيط (يستخدم حقل "code" للبحث)
{ "lang": "language" }
// ربط تفصيلي (حقل سبيكة مخصص)
{
"lang": {
"schemaName": "language",
"slugField": "code"
},
"slug": {
"schemaName": "article",
"slugField": "slug"
}
}
عند الجلب باستخدام documents.get(schema, identifier):
idcontent.codecontent.slugtitleيتيح ذلك مرونة في الإشارة إلى المستندات باستخدام أي معرّف فريد.