بناء موقع عربي وإنجليزي باستخدام Astro

دليل عملي لتنظيم المسارات والمحتوى واتجاه الصفحة وبيانات SEO في موقع Astro ثنائي اللغة.

كل المقالات
بناء موقع عربي وإنجليزي باستخدام Astro

الموقع متعدد اللغات ليس ملف ترجمة وزراً يبدل الكلمات فقط. لكل صفحة عنوان URL ولغة أساسية واتجاه ومحتوى وبيانات وصفية. إذا لم تتفق هذه الأجزاء، قد يرى المستخدم صفحة عربية داخل مستند معلن بالإنجليزية، أو ينتقل زر اللغة إلى الصفحة الرئيسية بدلاً من الصفحة المقابلة.

البنية التالية تجعل العربية هي اللغة الافتراضية، وتضع الإنجليزية تحت المسار /en/ دون روابط ترجمة وهمية.

ابدأ بعقد واضح للمسارات

حدد شكل الروابط قبل كتابة المكونات. في هذا المثال تكون العربية بلا بادئة، بينما تستخدم الإنجليزية بادئة ثابتة:

/                         الصفحة العربية الرئيسية
/services/                الخدمات بالعربية
/en/                      الصفحة الإنجليزية الرئيسية
/en/services/             الخدمات بالإنجليزية

يدعم Astro توجيه اللغات من خلال إعداد i18n. يجب أن تطابق أسماء اللغات بنية المجلدات التي تختارها.

// astro.config.mjs
import { defineConfig } from "astro/config";

export default defineConfig({
  site: "https://example.com",
  i18n: {
    defaultLocale: "ar",
    locales: ["ar", "en"],
    routing: {
      prefixDefaultLocale: false,
    },
  },
});

مع prefixDefaultLocale: false تبقى صفحات اللغة الافتراضية بلا /ar/. هذا قرار متعلق بعناوين URL، وليس أمراً يترجم المحتوى تلقائياً. ما زلت تحتاج إلى صفحات أو بيانات محتوى لكل لغة.

افصل النصوص عن المكونات

النصوص القصيرة، مثل عناصر التنقل وأسماء الأزرار، يمكن وضعها في كائن مكتوب بوضوح:

export const ui = {
  ar: {
    home: "الرئيسية",
    services: "الخدمات",
    contact: "تواصل",
  },
  en: {
    home: "Home",
    services: "Services",
    contact: "Contact",
  },
} as const;

export type Locale = keyof typeof ui;

ثم يقرأ المكون اللغة الحالية ولا يحتوي نسخاً متفرقة من النص نفسه:

---
import { ui, type Locale } from '../i18n/ui';

const locale = (Astro.currentLocale ?? 'ar') as Locale;
const t = ui[locale];
---

<nav aria-label={locale === 'ar' ? 'التنقل الرئيسي' : 'Main navigation'}>
  <a href={locale === 'ar' ? '/' : '/en/'}>{t.home}</a>
  <a href={locale === 'ar' ? '/services/' : '/en/services/'}>{t.services}</a>
</nav>

المقالات ودراسات الحالة تحتاج نموذجاً أوضح من كائن نصوص. أضف مفتاحاً ثابتاً يربط النسختين، ولا تفترض أن الـ slug سيكون متطابقاً دائماً:

---
title: "دليل الإتاحة"
lang: "ar"
translationKey: "accessibility-guide"
slug: "دليل-الإتاحة"
---

تستطيع النسخة الإنجليزية استخدام translationKey نفسه مع عنوان و slug مختلفين. عندها يستطيع زر اللغة البحث عن الصفحة المقابلة بدلاً من تركيب رابط قد لا يوجد.

أنشئ الروابط بأدوات Astro

توفر وحدة astro:i18n دوال مثل getRelativeLocaleUrl(). تحترم هذه الدوال إعدادات اللغة الافتراضية والبادئات، ولذلك تقلل أخطاء تركيب المسارات يدوياً.

---
import { getRelativeLocaleUrl } from 'astro:i18n';

const servicesAr = getRelativeLocaleUrl('ar', 'services');
const servicesEn = getRelativeLocaleUrl('en', 'services');
---

<a href={servicesAr}>الخدمات</a>
<a href={servicesEn}>Services</a>

زر تبديل اللغة يحتاج رابط الصفحة المقابلة، لا رابط الصفحة الرئيسية. إذا لم توجد ترجمة لمقال ما، اعرض رابطاً واضحاً إلى الأصل أو لا تعرض المبدل في تلك الصفحة. إنشاء صفحة فارغة أو نسخة آلية رديئة فقط لإكمال hreflang يضر القارئ.

اضبط اللغة والاتجاه في جذر المستند

العربية تحتاج lang="ar" وdir="rtl". الإنجليزية تحتاج lang="en" وdir="ltr".

---
const locale = Astro.currentLocale === 'en' ? 'en' : 'ar';
const direction = locale === 'ar' ? 'rtl' : 'ltr';
---

<html lang={locale} dir={direction}>
  <slot />
</html>

استخدم خصائص CSS المنطقية كي يعمل التخطيط في الاتجاهين:

.card {
  padding-inline: 1rem;
  margin-inline-start: 1.5rem;
  border-inline-start: 3px solid currentColor;
  text-align: start;
}

تجنب نسخ ملف CSS كامل للعربية. خصائص مثل margin-inline-start وpadding-inline-end تتبع اتجاه المستند. أما المحتوى الذي يكتبه المستخدم ولا تعرف لغته مسبقاً، فيمكن وضعه داخل عنصر يحمل dir="auto". للنصوص القصيرة المختلطة، مثل اسم مستخدم لاتيني داخل جملة عربية، يساعد عنصر <bdi> على عزل الاتجاه.

اجعل البيانات الوصفية خاصة بكل نسخة

كل نسخة مترجمة تحتاج عنواناً ووصفاً ورابطاً قانونياً يشير إلى نفسها. أضف hreflang فقط عندما تكون الصفحتان متكافئتين فعلاً.

<link rel="canonical" href="https://example.com/en/services/" />
<link rel="alternate" hreflang="ar" href="https://example.com/services/" />
<link rel="alternate" hreflang="en" href="https://example.com/en/services/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/services/" />

لا تضع صفحة noindex في sitemap. كذلك يجب أن تتفق قيمة inLanguage وعناوين JSON-LD مع لغة النص الظاهر والرابط القانوني. افحص HTML الناتج بعد البناء، لأن صحة مكون Astro وحدها لا تكشف اختلاف الرابط النهائي أو غياب صفحة مترجمة.

لا تستخدم الترجمة كسقوط صامت

سقوط اللغة إلى محتوى آخر قد يكون مناسباً لواجهة صغيرة، لكنه خطر في الصفحات العامة. صفحة إنجليزية تعرض مقالة عربية كاملة يجب أن تعلن ذلك بوضوح، وتستخدم lang="ar" على المقالة، ولا تقدم نفسها لمحرك البحث كترجمة إنجليزية.

في التطوير، اجعل غياب مفتاح الترجمة خطأً ظاهراً:

export function translate(locale: Locale, key: keyof typeof ui.ar) {
  const value = ui[locale][key];

  if (!value) {
    throw new Error(`Missing translation: ${locale}.${key}`);
  }

  return value;
}

إذا كان شكل كائني اللغتين ثابتاً في TypeScript، سيكشف المترجم بعض المفاتيح الناقصة قبل التشغيل. ما زالت هناك حاجة لاختبار الصفحات المبنية، خصوصاً المحتوى القادم من CMS.

اختبر المحتوى الحقيقي

اختبر هذه الحالات قبل النشر:

  1. افتح كل مسار عربي وإنجليزي مباشرة، وليس عبر التنقل فقط.
  2. بدّل اللغة من صفحة داخلية وتأكد من بقاء المستخدم في الموضوع نفسه.
  3. راجع العناوين الطويلة وأسماء الأشخاص وروابط البريد والأرقام داخل RTL.
  4. استخدم لوحة المفاتيح وتأكد من وضوح ترتيب التركيز.
  5. افحص <html lang> وdir و canonical و hreflang في HTML النهائي.
  6. قارن sitemap مع الصفحات القابلة للفهرسة.
  7. شغل فاحص الروابط على ناتج البناء.

المعيار العملي بسيط: يجب أن تكون كل نسخة صفحة مكتملة بلغتها، وليست غلافاً بلغة ومحتوى بلغة أخرى.

المراجع الرسمية