المؤلف:
    إنشاء:2026-03-20آخر تحديث:2026-10-08

    Intlayer مع Storybook

    جدول المحتويات

    لماذا تختار Intlayer مقارنة بالبدائل الأخرى؟

    بالمقارنة مع الحلول الرئيسية مثل storybook-react-i18next أو i18next، يعد Intlayer حلاً يأتي مزودًا بتحسينات متكاملة مثل:

    تم تحسين Intlayer للعمل بشكل مثالي مع Storybook من خلال تقديم أدوات تزيين القصص متعددة اللغات، وتبديل الإعدادات المحلية، وجميع الميزات اللازمة لتوسيع نطاق التدويل (i18n) عبر نظام التصميم الخاص بك.

    بدلاً من تحميل ملفات JSON ضخمة إلى صفحاتك، قم بتحميل المحتوى الضروري فقط. يساعد Intlayer في تقليل حجم حزمة JavaScript (bundle size) وصفحاتك بنسبة تصل إلى 50%.

    تحديد نطاق المحتوى لكل مكوّن على حدة يُسهّل صيانة التطبيقات الكبيرة. يمكنك نسخ مجلد ميزة بالكامل أو حذفه دون القلق بشأن مراجعة قاعدة بيانات المحتوى بأكملها. بالإضافة إلى ذلك، فإن Intlayer مكتوب بنظام أنواع قوي (Fully Typed) لضمان الدقة الكاملة لمحتواك.

    يؤدي وضع المحتوى إلى جانب المكونات (Co-location) إلى تقليل السياق المطلوب بواسطة نماذج اللغات الكبيرة (LLMs). كما يوفر Intlayer مجموعة أدوات متكاملة مثل واجهة سطر الأوامر (CLI) لفحص الترجمات المفقودة، وLSP، وخادم MCP، ومهارات الوكلاء (Agent Skills)، لتوفير أفضل تجربة تطوير لوكلاء الذكاء الاصطناعي.

    استخدم الأتمتة لترجمة المحتوى مباشرةً في مسار CI/CD الخاص بك باستخدام نموذج اللغة الكبير (LLM) الذي تختاره ووفق تكلفة مزود الذكاء الاصطناعي لديك. يقدم Intlayer أيضًا مترجمًا لأتمتة استخراج المحتوى، بالإضافة إلى منصة ويب للمساعدة في الترجمة في الخلفية.

    قد يؤدي تحميل ملفات JSON ضخمة في المكونات إلى مشكلات في الأداء وسرعة الاستجابة. يعمل Intlayer على تحسين تحميل المحتوى بدقة عند وقت البناء (Build Time).

    أكثر من مجرد حل i18n، يوفر Intlayer محررًا مرئيًا مستضافًا ذاتيًا ونظام إدارة محتوى كامل (CMS) لإدارة المحتوى متعدد اللغات في الوقت الفعلي، مما يجعل التعاون مع المترجمين وكتاب المحتوى وأعضاء الفريق الآخرين سلسًا للغاية. يمكن تخزين المحتوى محليًا و/أو عن بعد.

    لماذا تستخدم Intlayer مع Storybook؟

    Storybook هو الأداة القياسية في الصناعة لتطوير وتوثيق مكونات واجهة المستخدم بشكل معزول. يتيح لك دمجه مع Intlayer:

    • معاينة كل لغة مباشرة داخل لوحة Storybook باستخدام مبدل شريط الأدوات.
    • اكتشاف الترجمات المفقودة قبل أن تصل إلى مرحلة الإنتاج.
    • توثيق المكونات متعددة اللغات بمحتوى حقيقي وآمن من حيث النوع (type-safe) بدلاً من السلاسل النصية الثابتة.

    الإعداد خطوة بخطوة

    1. تثبيت التبعيات

      bash
      npm install intlayer react-intlayer
      npm install vite-intlayer --save-dev
      
      الحزمةالدور
      intlayerالأساس - التكوين، تجميع المحتوى، واجهة سطر الأوامر (CLI)
      react-intlayerروابط React - IntlayerProvider و useIntlayer hook
      vite-intlayerإضافة Vite - تراقب وتجمع ملفات إعلان المحتوى
    2. إنشاء تكوين Intlayer

      أنشئ ملف intlayer.config.ts في جذر مشروعك (أو داخل حزمة نظام التصميم الخاص بك):

      intlayer.config.ts
      import { Locales, type IntlayerConfig } from "intlayer";
      
      const config: IntlayerConfig = {
        internationalization: {
          locales: [
            Locales.ENGLISH,
            Locales.FRENCH,
            Locales.SPANISH,
            // أضف المزيد من اللغات حسب الحاجة
          ],
          defaultLocale: Locales.ENGLISH,
        },
        content: {
          contentDir: ["./src"], // مكان وجود ملفات *.content.ts الخاصة بك
        },
      };
      
      export default config;
      
      للحصول على القائمة الكاملة للخيارات، راجع مرجع التكوين.
    3. إضافة إضافة Vite إلى Storybook

      تتيح لك وظيفة viteFinal في Storybook توسيع تكوين Vite الداخلي. قم باستيراد وإضافة إضافة intlayer() هناك:

      .storybook/main.ts
      import type { StorybookConfig } from "@storybook/react-vite";
      import { defineConfig, mergeConfig } from "vite";
      import { intlayer } from "vite-intlayer";
      
      const config: StorybookConfig = {
        stories: ["../src/**/*.stories.@(js|jsx|ts|tsx)"],
        addons: [
          "@storybook/addon-essentials",
          // …إضافات أخرى
        ],
        framework: {
          name: "@storybook/react-vite",
          options: {},
        },
      
        async viteFinal(baseConfig, { configType }) {
          const env = {
            command: configType === "DEVELOPMENT" ? "serve" : "build",
            mode: configType === "DEVELOPMENT" ? "development" : "production",
          } as const;
      
          const viteConfig = defineConfig(() => ({
            plugins: [
              intlayer({
                proxy: {
                  ignore: (req) => req.url?.startsWith("/api"),
                },
              }),
            ],
          }));
      
          return mergeConfig(baseConfig, viteConfig(env));
        },
      };
      
      export default config;
      

      تراقب إضافة intlayer() ملفات *.content.ts الخاصة بك وتعيد بناء القواميس تلقائيًا كلما تغيرت أثناء عملية تطوير Storybook.

    4. إضافة `IntlayerProvider` ومبدل اللغة في شريط الأدوات

      ملف preview في Storybook هو المكان المناسب لتغليف كل قصة (story) بـ IntlayerProvider وعرض مبدل اللغة في شريط الأدوات:

      .storybook/preview.tsx
      import type { Preview, StoryContext } from "@storybook/react";
      import { IntlayerProvider } from "react-intlayer";
      
      const preview: Preview = {
        // تغليف كل قصة داخل IntlayerProvider
        decorators: [
          (Story, context: StoryContext) => {
            const locale = context.globals.locale ?? "en";
            return (
              <IntlayerProvider locale={locale}>
                <Story />
              </IntlayerProvider>
            );
          },
        ],
      
        // عرض مبدل اللغة في شريط أدوات Storybook
        globalTypes: {
          locale: {
            description: "اللغة النشطة",
            defaultValue: "en",
            toolbar: {
              title: "اللغة",
              icon: "globe",
              items: [
                { value: "en", title: "English" },
                { value: "fr", title: "Français" },
                { value: "es", title: "Español" },
              ],
              dynamicTitle: true,
            },
          },
        },
      
        parameters: {
          controls: {
            matchers: {
              color: /(background|color)$/i,
              date: /Date$/i,
            },
          },
        },
      };
      
      export default preview;
      
      يجب أن تتطابق قيم locale مع اللغات المعلنة في ملف intlayer.config.ts الخاص بك.
    1. تثبيت التبعيات

      bash
      npm install intlayer react-intlayer
      npm install @intlayer/webpack --save-dev
      
    2. إنشاء تكوين Intlayer

      أنشئ ملف intlayer.config.ts في جذر مشروعك:

      intlayer.config.ts
      import { Locales, type IntlayerConfig } from "intlayer";
      
      const config: IntlayerConfig = {
        internationalization: {
          locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],
          defaultLocale: Locales.ENGLISH,
        },
        content: {
          contentDir: ["./src"],
        },
      };
      
      export default config;
      
    3. تكوين Webpack لـ Storybook

      بالنسبة لإعدادات Storybook القائمة على Webpack (مثل @storybook/react-webpack5)، قم بتوسيع تكوين webpack عبر webpackFinal لإضافة أسماء Intlayer البديلة والـ loader:

      .storybook/main.ts
      import type { StorybookConfig } from "@storybook/react-webpack5";
      import { IntlayerPlugin } from "@intlayer/webpack";
      
      const config: StorybookConfig = {
        stories: ["../src/**/*.stories.@(js|jsx|ts|tsx)"],
        addons: ["@storybook/addon-essentials"],
        framework: {
          name: "@storybook/react-webpack5",
          options: {},
        },
      
        webpackFinal: async (baseConfig) => {
          baseConfig.plugins = [...(baseConfig.plugins ?? []), new IntlayerPlugin()];
          return baseConfig;
        },
      };
      
      export default config;
      
    4. إضافة `IntlayerProvider` ومبدل اللغة في شريط الأدوات

      كما هو الحال في إعداد Vite - أضف الـ decorator ونوع اللغة العالمي في .storybook/preview.tsx:

      .storybook/preview.tsx
      import type { Preview, StoryContext } from "@storybook/react";
      import { IntlayerProvider } from "react-intlayer";
      
      const preview: Preview = {
        decorators: [
          (Story, context: StoryContext) => {
            const locale = context.globals.locale ?? "en";
            return (
              <IntlayerProvider locale={locale}>
                <Story />
              </IntlayerProvider>
            );
          },
        ],
      
        globalTypes: {
          locale: {
            description: "اللغة النشطة",
            defaultValue: "en",
            toolbar: {
              title: "اللغة",
              icon: "globe",
              items: [
                { value: "en", title: "English" },
                { value: "fr", title: "Français" },
                { value: "es", title: "Español" },
              ],
              dynamicTitle: true,
            },
          },
        },
      };
      
      export default preview;
      

    إعلان المحتوى

    أنشئ ملف *.content.ts بجانب كل مكون. سيتعرف عليه Intlayer تلقائيًا أثناء التجميع.

    src/components/CopyButton/CopyButton.content.ts
    import { type Dictionary, t } from "intlayer";
    
    const copyButtonContent = {
      key: "copy-button",
      content: {
        label: t({
          en: "Copy content",
          fr: "Copier le contenu",
          es: "Copiar contenido",
        }),
      },
    } satisfies Dictionary;
    
    export default copyButtonContent;
    
    لمزيد من تنسيقات وميزات إعلان المحتوى، راجع توثيق إعلان المحتوى.

    استخدام useIntlayer في مكون

    src/components/CopyButton/index.tsx
    "use client";
    
    import { type FC } from "react";
    import { useIntlayer } from "react-intlayer";
    
    type CopyButtonProps = {
      content: string;
    };
    
    export const CopyButton: FC<CopyButtonProps> = ({ content }) => {
      const { label } = useIntlayer("copy-button");
    
      return (
        <button
          onClick={() => navigator.clipboard.writeText(content)}
          aria-label={label.value}
          title={label.value}
        >
          نسخ
        </button>
      );
    };
    

    تعيد useIntlayer القاموس المجمع للغة الحالية المقدمة من أقرب IntlayerProvider. يؤدي تبديل اللغة في شريط أدوات Storybook إلى إعادة رندرة القصة تلقائيًا بالترجمات المحدثة.

    كتابة القصص للمكونات المدوّلة

    مع وجود الـ IntlayerProvider decorator، تعمل قصصك تمامًا كما كانت من قبل. يتحكم مبدل اللغة في شريط الأدوات في اللغة النشطة للوحدة بأكملها:

    src/components/CopyButton/CopyButton.stories.tsx
    import type { Meta, StoryObj } from "@storybook/react";
    import { CopyButton } from ".";
    
    const meta: Meta<typeof CopyButton> = {
      title: "Components/CopyButton",
      component: CopyButton,
      tags: ["autodocs"],
      argTypes: {
        content: { control: "text" },
      },
    };
    
    export default meta;
    type Story = StoryObj<typeof CopyButton>;
    
    /** القصة الافتراضية - قم بتبديل اللغة في شريط الأدوات لمعاينة الترجمات. */
    export const Default: Story = {
      args: {
        content: "npm install intlayer react-intlayer",
      },
    };
    
    /** عرض الزر داخل كود برمجى، وهي حالة استخدام شائعة في الواقع. */
    export const InsideCodeBlock: Story = {
      render: (args) => (
        <div style={{ position: "relative", display: "inline-block" }}>
          <pre style={{ background: "#1e1e1e", color: "#fff", padding: "1rem" }}>
            <code>{args.content}</code>
          </pre>
          <CopyButton
            content={args.content}
            style={{ position: "absolute", top: 8, right: 8 }}
          />
        </div>
      ),
      args: {
        content: "npx intlayer init",
      },
    };
    
    ترث كل قصة الـ locale العالمي من شريط الأدوات، لذا يمكنك التحقق من كل لغة دون تغيير أي كود في القصة.

    اختبار الترجمات في القصص

    استخدم وظائف play في Storybook للتأكد من رندرة النص المترجم الصحيح للغة معينة:

    src/components/CopyButton/CopyButton.stories.tsx
    import type { Meta, StoryObj } from "@storybook/react";
    import { expect, within } from "@storybook/test";
    import { CopyButton } from ".";
    
    const meta: Meta<typeof CopyButton> = {
      title: "Components/CopyButton",
      component: CopyButton,
      tags: ["autodocs"],
    };
    
    export default meta;
    type Story = StoryObj<typeof CopyButton>;
    
    export const AccessibleLabel: Story = {
      args: { content: "Hello World" },
      play: async ({ canvasElement }) => {
        const canvas = within(canvasElement);
        const button = canvas.getByRole("button");
    
        // التأكد من أن الزر لديه اسم متاح غير فارغ
        await expect(button).toHaveAccessibleName();
        // التأكد من أن الزر غير معطل
        await expect(button).not.toBeDisabled();
        // التأكد من إمكانية الوصول عبر لوحة المفاتيح
        await expect(button).toHaveAttribute("tabindex", "0");
      },
    };
    

    موارد إضافية