← كل المقالات

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

دليل إنشاء موقع Astro عربي وإنجليزي يدعم RTL، مع مسارات ثابتة ومجموعات محتوى وروابط canonical وhreflang وخريطة موقع صحيحة.

يبني بكري عبدالسلام مواقع الويب والتطبيقات والتكاملات ومنتجات ووردبريس، ويوثق مركز بكري التقني القرارات التقنية وراء هذا العمل.

الخلاصة

اجعل اللغة جزءاً من نموذج المحتوى، واربط الترجمتين بمفتاح ثابت، ثم اشتق المسارات وlang وdir وcanonical وhreflang من المصدر نفسه.

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

سنستخدم الإنجليزية كلغة افتراضية بلا /en/، والعربية داخل /ar/. تصبح روابط المقال مثلاً /blog/example/ و/ar/blog/example/.

اضبط التعدد اللغوي في Astro

حدد Origin واللغات وسياسة الروابط:

// astro.config.mjs
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';

export default defineConfig({
  site: 'https://example.com',
  trailingSlash: 'always',
  output: 'static',
  i18n: {
    locales: ['en', 'ar'],
    defaultLocale: 'en',
    routing: {
      prefixDefaultLocale: false,
    },
  },
  integrations: [
    sitemap({
      i18n: {
        defaultLocale: 'en',
        locales: {
          en: 'en-US',
          ar: 'ar',
        },
      },
    }),
  ],
});

تعني prefixDefaultLocale: false أن اللغة الافتراضية لن تظهر في الرابط. قرر ذلك قبل إطلاق الموقع؛ تغيير السياسة لاحقاً يحتاج 301 Redirect لكل الروابط المفهرسة.

اجمع نصوص الواجهة ومساراتها في مكان واحد

استخدم Dictionary مكتوبة الأنواع:

export type Locale = 'en' | 'ar';

export const ui = {
  en: {
    dir: 'ltr',
    nav: { home: 'Home', blog: 'Articles' },
    routes: { home: '/', blog: '/blog/' },
  },
  ar: {
    dir: 'rtl',
    nav: { home: 'الرئيسية', blog: 'المقالات' },
    routes: { home: '/ar/', blog: '/ar/blog/' },
  },
} as const;

لا تكرر شروط اللغة داخل كل Component. يجب أن يحدد Locale واحد النص والمسار والاتجاه حتى لا تحمل الصفحة العربية Metadata إنجليزية بالخطأ.

مثّل المقالات المترجمة داخل مجموعة محتوى

استخدم Content Collection مع Schema:

const articles = defineCollection({
  loader: glob({
    base: './src/content/articles',
    pattern: '**/*.{md,mdx}',
  }),
  schema: z.object({
    title: z.string(),
    description: z.string(),
    locale: z.enum(['en', 'ar']),
    routeSlug: z.string(),
    translationKey: z.string(),
    publishedAt: z.coerce.date(),
    draft: z.boolean().default(false),
  }),
});

ثم نظم الملفات:

src/content/articles/
├── en/astro-i18n-arabic-english-rtl.md
└── ar/astro-i18n-arabic-english-rtl.md

يربط translationKey المعنى بين النسختين، بينما يحدد routeSlug الرابط. يمكن استخدام Slug إنجليزية في النسختين لتجنب Encoding، لكن افصل الحقلين حتى لا تربط هوية الترجمة باسم الملف.

لا تجعل المقال العربي ترجمة آلية حرفية. احتفظ بالأوامر كما هي، واشرح القرارات بلغة عربية طبيعية.

ولّد صفحات ثابتة لكل لغة

صفحة English Route:

---
import { getCollection } from 'astro:content';

export async function getStaticPaths() {
  const articles = await getCollection(
    'articles',
    ({ data }) => data.locale === 'en' && !data.draft,
  );

  return articles.map((article) => ({
    params: { slug: article.data.routeSlug },
    props: { article },
  }));
}
---

ضعها في src/pages/blog/[slug].astro. أنشئ نسخة عربية داخل src/pages/ar/blog/[slug].astro مع Filter locale === 'ar'.

يضمن Static Generation ظهور المحتوى داخل HTML دون JavaScript، ويسمح بفحص التصادمات والروابط وقت Build.

اضبط lang وdir في عنصر HTML

---
const { locale = 'en' } = Astro.props;
const dir = locale === 'ar' ? 'rtl' : 'ltr';
---

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

استخدم CSS Logical Properties:

.article-card {
  padding-inline: 1.25rem;
  border-inline-start: 3px solid var(--accent);
}

.article-content pre,
.article-content code {
  direction: ltr;
  text-align: left;
}

اختبر النص المختلط والأرقام وInline Code والجداول والأوامر الطويلة. RTL سلوك للمحتوى وليس text-align: right فقط.

اربط الرابط الأساسي وبدائل اللغة

كل صفحة لها Self Canonical وAlternate للنسخة الموجودة:

---
const canonical = new URL(Astro.url.pathname, 'https://example.com');
const alternatePath =
  locale === 'en'
    ? `/ar${Astro.url.pathname}`
    : Astro.url.pathname.replace(/^\/ar(?=\/)/, '') || '/';
const alternate = new URL(alternatePath, 'https://example.com');
---

<link rel="canonical" href={canonical} />
<link rel="alternate" hreflang={locale} href={canonical} />
<link
  rel="alternate"
  hreflang={locale === 'en' ? 'ar' : 'en'}
  href={alternate}
/>
<link
  rel="alternate"
  hreflang="x-default"
  href={locale === 'en' ? canonical : alternate}
/>

لا تفترض وجود ترجمة من تشابه Slug. ابحث عنها عبر translationKey ولا تعرض Alternate إذا لم تكن الصفحة موجودة.

Canonical الصفحة العربية يجب أن تشير إلى نفسها. Hreflang تربط النسختين لكنها لا تلغي فهرسة إحداهما.

أكمل البيانات المنظمة والتنقل

ضع inLanguage الصحيح في Article Schema، واستخدم العنوان والوصف المحليين، واربط mainEntityOfPage بالـCanonical.

Language Switcher يجب أن ينتقل إلى نفس المقال في اللغة الأخرى، لا إلى Homepage. أنشئ RSS مستقلاً لكل لغة وتأكد أن Sitemap تحتوي المسارين.

راجع Build آلياً للتأكد من:

  • عدم تكرار (locale, routeSlug).
  • ترجمة واحدة فقط لكل translationKey في كل لغة.
  • Canonical صحيحة.
  • Alternate يعيد 200.
  • lang وdir صحيحان.
  • Drafts غير موجودة في Route وRSS وSitemap.

يكمل دليل بنية المحتوى في Astro نموذج Collections، بينما يساعد دليل أداء Astro على منع إضافة JavaScript غير ضرورية.

أخطاء شائعة

تجنب Canonical العربية على الإنجليزية، وتكرار منطق بناء المسار، وإرسال Language Switcher إلى الصفحة الرئيسية، وتحميل مكتبة ترجمة Client-Side لمحتوى Static، وجعل Code Blocks تعمل RTL.

المصدر الواحد للمسارات هو ما يجعل i18n قابلة للصيانة.

أسئلة شائعة

هل يمكن أن يستخدم المقال العربي والإنجليزي Slug نفسها؟

نعم، لأن Prefix اللغة تجعل الرابطين مختلفين، وSlug إنجليزية تتجنب Encoding. استخدم translationKey منفصلة لربط المحتوى ولا تعتمد على Slug وحدها.

هل يحتاج موقع Astro Static إلى مكتبة i18n في المتصفح؟

ليس للمحتوى المترجم الثابت. توفر Build-time Routes وContent Collections وUI Dictionary صفحات محلية كاملة مع JavaScript أقل.

مراجع رسمية

لديك سؤال عن هذا الدليل أو فكرة لتعاون تقني؟ تواصل مع بكري عبر المركز التقني.

نهاية الملاحظة.