- مقدمة
- العرض
- MVP
- خارطة الطريق
- متطلبات المنتج
- ابدأ الآن
- البيئة المحلية
- البنية المعمارية
- الهيكل
- الأنماط
- الحزمة التقنية
- قاعدة البيانات
- تعدّد المستأجرين
- الفهرس (Catalog)
- العمل دون اتصال
- المصادقة
- التهيئة (Onboarding)
- تدفق التكامل (Integration Flow)
- التهيئة (Provision)
- القبول
- الحضور
- الامتثال التنظيمي
- الملف الشخصي
- الاجتماعات
- إدارة الرسوم
- الفاتورة
- التدويل
- الترجمة
- دليل الترجمة
- أمان قاعدة البيانات
- المساهمة
- هَوْغوارتس
- عرض حي
- الإلهام
تعمل الترجمة على طبقتين: ملفات JSON القاموس للواجهة الثابتة (تطابق ar/en يفرضه اختبار في CI) ومحرك localize() المُجمَّع لمحتوى قاعدة البيانات الديناميكي عبر مخبأ ثلاثي الطبقات (ذاكرة LRU ← جدول Translation ← Google Translate). لم يعد بإمكان الانحراف أن يصل للإنتاج: تطابق مفاتيح en/ar، والبقايا غير المترجمة، وعدّادات السلاسل المكتوبة يدويًا، وفئات RTL الفيزيائية كلها محروسة ضمن pnpm test وضمن CI. المتبقي هو مسح السلاسل اليدوية الذي تتعقبه أرقام الأساس — شغّل npx tsx scripts/i18n-hardcoded-ratchet.ts --by-dir للعدد الحيّ.
البنية المعمارية
| الطبقة | المحتوى | الآلية |
|---|---|---|
| 1 — واجهة ثابتة | sidebar، رؤوس الجداول، شارات الحالة، التسميات | JSON قاموس → getDictionary() → dictionary.key |
| 2 — محتوى ديناميكي | إعلانات، مواد، أقسام، فعاليات | تخزين بلغة واحدة → localize()/getLabels() المُجمَّعة → LRU → كاش → Google |
ملفات البنية التحتية
| الملف | الغرض |
|---|---|
src/components/translation/localize.ts | localize()/localizeOne() المُجمَّعة — المفضّلة |
src/components/translation/registry.ts | خريطة TRANSLATABLE (النموذج ← الحقول) |
src/components/translation/prewarm.ts | تعبئة المخبأ عند الكتابة عبر after() |
src/components/translation/person.ts | getNames()/getLabels() — مُجمَّعة |
src/components/translation/display.ts | getText()، getFields() — للقيم المفردة فقط |
src/components/translation/actions.ts | نواة translate() (LRU ← كاش ← Google) |
src/components/translation/google.ts | عميل Google: مهلة، تقسيم دفعات، سياسة إعادة |
src/components/translation/util.ts | withLang، detectScript |
src/components/internationalization/helpers/index.ts | ValidationHelper، ToastHelper، ErrorHelper |
src/components/internationalization/namespaces.ts | سجل نطاقات الأسماء الوحيد (خادم + عميل) |
src/components/internationalization/dictionaries.ts | محمّلات قاموس بنطاق المسار (ملفوفة بـ cache()) |
فجوات التغطية
| الفجوة | الحالة |
|---|---|
نص الأزرار (Save، Cancel، Delete…) | المفاتيح موجودة في dictionary.common.* |
| التحقّق (رسائل Zod) | يحتاج إعادة هيكلة بمصنع schema |
| خيارات config / select | يحتاج مصانع خيارات |
| تسميات النماذج وplaceholders | تبنٍّ جزئي |
| أخطاء server actions | يحتاج نمط رمز خطأ |
| تبويبات / تسميات مجموعات المعالج | معلّق |
| الحالات الفارغة | معلّق |
| رسائل Toast | يحتاج توصيل ToastHelper |
عناصر sidebar Cards وLab تفتقد أيضاً مفاتيح القاموس. شغّل /i18n-check للعدد الحالي لكل منطقة.
ترتيب الأولويات
- نص الأزرار — المفاتيح موجودة بالفعل في
dictionary.common.*. - تسميات مجموعات المعالج — رؤية عالية.
- تسميات النماذج — أكبر أثر على UX.
- خيارات select — يحتاج مصنع config.
- التحقّق — يحتاج إعادة هيكلة بمصنع schema.
- أخطاء server actions — يحتاج نمط رمز خطأ.
- الحالات الفارغة.
- رسائل Toast — يحتاج توصيل القاموس.
- توسيع
getTextلبقية ملفات المحتوى.
نمط النموذج
مرّر شرائح القاموس إلى نماذج العميل.
// content.tsx
const dictionary = await getDictionary(lang)
return <StudentForm dictionary={dictionary.school.students} />
// form.tsx
<FormLabel>{dictionary.form.name}</FormLabel>
<Input placeholder={dictionary.form.namePlaceholder} {...field} />نمط Select
صدّر مصانع خيارات، لا مصفوفات ثابتة.
export const getGenderOptions = (d: Dictionary["school"]) => [
{ value: "male", label: d.students.gender.male },
{ value: "female", label: d.students.gender.female },
]نمط التحقّق
تتحوّل schemas إلى مصانع تستقبل ValidationHelper. يعمل بالفعل في onboarding وauth.
import { getValidationMessages } from "@/components/internationalization/helpers"
export const createStudentSchema = (
v: ReturnType<typeof getValidationMessages>
) =>
z.object({
name: z.string().min(1, v.required()),
email: z.string().email(v.email()),
phone: z.string().min(10, v.minLength(10)),
})العائق: ملفات التحقّق عبر اللوحة بحاجة للتحوّل من schemas على مستوى الوحدة إلى مصانع.
نمط Toast
import { getToastMessages } from "@/components/internationalization/helpers"
const t = useMemo(() => getToastMessages(dictionary), [dictionary])
if (result.success) toast.success(t.success.student.created())
else toast.error(t.error.student.createFailed())العائق: toasts تعمل على العميل؛ ToastHelper يحتاج القاموس في نطاقه.
نمط أخطاء server action
أعِد رموز أخطاء؛ ترجِم على العميل.
// actions.ts
if (!session?.user) return { success: false, errorCode: "NOT_AUTHENTICATED" }
if (!schoolId) return { success: false, errorCode: "MISSING_SCHOOL_CONTEXT" }
// form.tsx
const ERROR_MAP: Record<string, string> = {
NOT_AUTHENTICATED: dictionary.messages.errors.auth.notAuthenticated,
MISSING_SCHOOL_CONTEXT:
dictionary.messages.errors.tenant.missingSchoolContext,
}
toast.error(
ERROR_MAP[result.errorCode] ?? dictionary.messages.errors.server.internalError
)الحالة لكل منطقة
كل المناطق الـ21 (Dashboard، Students، Teachers، Classes، Subjects، Announcements، Events، Assignments، Parents، Grades، Classrooms، Exams، Finance، Attendance، Timetable، Profile، Settings، Notifications، Admission، School Config، Reports) تستخدم القاموس لرؤوس الجداول وsidebar لكنها جزئية على getText، النماذج، الـselects، التحقّق، الـtoasts، والإجراءات. Students/Teachers/Classes/Subjects/Announcements/Parents/Classrooms/Exams/Attendance/Profile تستخدم getText لبعض الأسطح؛ الباقي بانتظار display-text.
مسارات المعالج (/students/add، /teachers/add، /exams/manage، /exams/qbank، /finance/invoice) فيها نماذج، selects، تحقّق، toasts، وأخطاء إجراءات مكتوبة يدوياً.
الحواجز الواقية
| الطبقة | ما تفعله |
|---|---|
.claude/rules/translation.md | قاعدة بنطاق المسار تنطلق على تعديلات src/components/** وsrc/app/** |
.claude/hooks/check-i18n.sh | hook PostToolUse يرفع رايات على سلاسل <FormLabel> وtoast.success/error المكتوبة يدوياً |
CLAUDE.md gotcha #12 | يذكّر بأن كل نص واجهة يجب أن يستخدم مفاتيح القاموس |