מחבר:
    נוצר:2026-09-23עדכון אחרון:2026-11-03

    האם Intlayer קלה יותר מ-Paraglide?

    כן.

    ל-Paraglide יש מוניטין חיובי כפתרון ה-i18n הקל ביותר בסביבה, ובמבט ראשון הבנצ'מרק מאשר זאת: גודל הספרייה שלה קרוב לאפס. אך גודל ספרייה של אפס אינו אומר שאפס בייטים נשלחים לדפדפן. המשמעות היא שהבייטים הללו נמצאים במקום שבו המדד אינו בודק.

    נקודות מפתח

    גודל הספרייה מוסתר, לא נעלם:

    Paraglide מייצרת את זמן הריצה ופונקציות ההודעות שלה ישירות לתוך קוד המקור שלכם. קוד זה נשלח לדפדפן, אך הוא נספר כקוד שלכם, ולא כשל הספרייה.

    היעדר Provider אינו ניצחון חינמי:

    כל קריאה ל-m.my_key() מפענחת את ה-locale בעצמה, וקוראת את ה-cookie או ה-storage עבור כל צומת (node) שמרונדר, במקום לקרוא אותו פעם אחת מ-context.

    ללא טעינה דינמית:

    Paraglide מייבאת כל שפה (locale) של הודעה לתוך חבילת הלקוח שלכם. Intlayer עם importMode: 'dynamic' או 'fetch' טוענת אך ורק את השפה שמרונדרת בפועל.

    Tree-shaking אינו מובטח:

    בחלק ממבחני הביצועים שלנו, ה-Tree-shaking שהובטח ב-Paraglide לא פעל בפועל. בדקו את ה-bundle שלכם.

    לאן הולך המשקל של Paraglide?

    בדוחות מבחני הביצועים, מדד "גודל הספרייה" בודק את ה-provider וה-hooks של כל ספריית i18n בקומפוננטה ריקה, לפני הוספת תוכן כלשהו.

    ספרייה (TanStack Start)גודל ספרייה (gz)גודל ספרייה (min)
    @inlang/paraglide-js@2.15.11.8 KB4.5 KB
    react-intlayer@9.5.15.0 KB15.2 KB

    בקריאה מבודדת, Paraglide מנצחת. אך Paraglide היא קומפיילר: היא קוראת את קובצי ה-messages/*.json שלכם וכותבת תיקיית paraglide/ לתוך המאגר שלכם, המכילה runtime.js (זיהוי locale, אסטרטגיות cookie ו-storage, לוקליזציה של URLs) ופונקציית JavaScript אחת לכל הודעה.

    bash
    src/paraglide/
    ├── runtime.js      # זיהוי locale, אסטרטגיות, עזרי URL
    ├── server.js
    ├── messages.js     # ייצוא מחדש של כל הודעה
    └── messages/
        ├── _index.js
        ├── en.js
        └── fr.js
    

    מכיוון שקוד זה יושב בתוך תיקיית ה-src/ שלכם ואתם מייבאים אותו באמצעות נתיב יחסי, הבנדלר מייחס אותו לאפליקציה שלכם, ולא לחבילת node_modules. עמודת גודל הספרייה מציגה כמעט אפס, בעוד שאותו היגיון קוד עדיין נשלח במלואו ב-bundle של הדף שלכם.

    יצירת קוד (code generation) אינה רעיון רע כשלעצמו: זמן הריצה שנוצר כולל רק את הלוגיקה שהקונפיגורציה שלכם דורשת (אסטרטגיית קידומת, cookie לעומת local storage וכו'). Intlayer מגיעה לאותה תוצאה בדרך אחרת, על ידי הזרקת משתני סביבה בזמן בנייה כך שהבנדלר מסיר את הענפים שאינם בשימוש בקונפיגורציה שלכם. שתי הגישות מסתיימות בקוד קל פי 3 עד 10 מ-i18next או next-intl.

    לכן, ההשוואה ההוגנת אינה גודל הספרייה. היא קוד ה-JavaScript שנשלח בפועל לכל דף.

    משקל דף, במדידה בפועל

    אפליקציית TanStack Start, 10 דפים, נמדדה בנתיבי en ו-fr, מכווץ ב-gzip:

    הגדרהממוצע JS לדף (gz)מעל הבסיסדליפת שפותדליפת דפים אחרים
    Base (ללא i18n)111.0 KB-0.0%0.0%
    paraglide (כל אסטרטגיה)125.1 KB+14.1 KB49.7%0.0%
    intlayer (importMode: static)125.8 KB+14.8 KB50.0%0.0%
    intlayer (importMode: dynamic)118.6 KB+7.6 KB0.0%0.0%

    Next.js 16 App Router, אותה אפליקציה:

    הגדרהממוצע JS לדף (gz)מעל הבסיס
    Base (ללא i18n)141.0 KB-
    paraglide-next155.3 KB+14.3 KB
    next-intlayer141.3 KB+0.3 KB

    מדד

    טעינת JSON דינמית

    טעינה עצלה של תרגומים בזמן ריצה

    JSON תחום (namespacing)

    מרחבי שמות תרגום לכל דף

    מהו המדד הזה?

    הגודל הכולל לאחר דחיסת gzip של ה-bundle של ספריית הבינאום. הוא כולל רק את ה-provider ואת לוגיקת שליפת התוכן לאחר tree-shaking ומיזעור.

    למה זה חשוב?

    ספרייה קטנה יותר מקטינה את מטען ה-JavaScript ההתחלתי, מה שמוביל לזמני הורדה והרצה מהירים יותר בצד הלקוח.

    הצגה כ

    הנתונים המלאים מופיעים בדוח ביצועי TanStack Start ובדוח ביצועי Next.js. ניתן לבדוק כל bundle במאגר מבחני הביצועים.

    שני דברים בולטים מיד:

    • במצב static, Intlayer שולחת למעשה את אותו התוכן כמו Paraglide (125.8 KB לעומת 125.1 KB). זה צפוי: שתיהן כוללות את כל השפות של ההודעות שבהן הדף משתמש.
    • Paraglide נשארת על 125.1 KB בכל אסטרטגיה שהיא, מכיוון שאין לה מצב דינמי. כל שורה בטבלה לעיל היא השורה הסטטית.

    ללא Provider: רעיון שנשמע טוב אבל אינו כזה

    ל-Paraglide אין provider. אתם פשוט מייבאים הודעה וקוראים לה:

    Hero.tsx
    import { m } from "../paraglide/messages.js";
    
    export const Hero = () => (
      <section>
        <h1>{m.hero_title()}</h1>
        <p>{m.hero_description()}</p>
        <button>{m.hero_cta()}</button>
      </section>
    );
    

    ללא context, ללא wrapper וללא hook. זה נראה פשוט יותר. אבל השפה עדיין צריכה להגיע מאיפשהו. כל פונקציית הודעה שנוצרת נראית בקירוב כך (בצורה מופשטת):

    paraglide/messages/_index.js
    export const hero_title = (inputs = {}, options = {}) => {
      const locale = options.locale ?? getLocale(); // מפוענח בכל קריאה מחדש
    
      if (locale === "en") return en.hero_title(inputs);
      if (locale === "fr") return fr.hero_title(inputs);
      // ...ענף אחד לכל שפה
    };
    

    והפונקציה getLocale() עוברת על האסטרטגיות שהוגדרו (cookie, local storage, URL, שפת בסיס) כדי למצוא את השפה הנוכחית. המשמעות היא שכל צומת טקסט שאתם מרנדרים (<>{m.my_key()}</>) מריץ פענוח שפה עצמאי משלו, כולל קריאת document.cookie בדפדפן. דף עם 200 מחרוזות מתורגמות מפענח את השפה 200 פעמים בכל רינדור, ושוב בכל רינדור מחדש (re-render).

    ספרייה המבוססת על provider קוראת את השפה פעם אחת, שומרת אותה ב-context (או ב-signal, או ב-store), וכל צומת קורא ערך שכבר נמצא בזיכרון. ה-provider עולה כמה מאות בייטים. הוויתור עליו עולה ב-CPU בכל רינדור, וזה ניכר במבחני הביצועים: זמני טעינת הדף והחלפת השפה של Paraglide מפגרים בעקביות אחרי אלו של Intlayer ב-TanStack Start (22.1 ms לעומת 14.6 ms בטעינת דף, 4.3 ms לעומת 3.2 ms בתגובתיות E2E).

    חוויית מפתח

    מקור האמת (source of truth) של Paraglide הוא קובצי JSON, אך לעולם אינכם מייבאים את ה-JSON ישירות. אתם מייבאים את קובץ ה-.js שנוצר:

    messages/en.json
    {
      "hero_title": "Ship your app in every language"
    }
    
    messages/fr.json
    {
      "hero_title": "Publiez votre app dans toutes les langues"
    }
    
    Hero.tsx
    // נוצר רק לאחר שהקומפיילר ייצר אותו מחדש מתוך ה-JSON
    import { m } from "../paraglide/messages.js";
    
    export const Hero = () => <h1>{m.hero_title()}</h1>;
    
    Hero.content.ts
    import { t, type Dictionary } from "intlayer";
    
    export default {
      key: "hero",
      content: {
        title: t({
          en: "Ship your app in every language",
          fr: "Publiez votre app dans toutes les langues",
        }),
      },
    } satisfies Dictionary;
    
    Hero.tsx
    import { useIntlayer } from "react-intlayer";
    
    export const Hero = () => {
      const { title } = useIntlayer("hero");
    
      return <h1>{title}</h1>;
    };
    

    למחזור עבודה זה יש מחיר:

    • כל שינוי בקובץ JSON מחייב יצירה מחדש (regeneration) לפני שה-import מתפענח או שהטיפוסים מתעדכנים.
    • את תיקיית paraglide/ שנוצרת יש לבצע לה commit (מה שמוביל להתנגשויות merge בקבצים מיוצרים בכל PR שנוגע בטקסט), או להתעלם ממנה (מה שמחייב שלב יצירה לפני כל בדיקת טיפוסים, בדיקה ומשימת CI).
    • כל מחרוזת הופכת לקריאת פונקציה. קבועים הופכים ל-m.key() בכל מקום, כולל במקומות שבהם ערך פשוט היה מספיק.

    Tree Shaking: בדקו את ה-Bundle שלכם

    ההבטחה העיקרית של Paraglide היא שהודעות ללא שימוש מוסרות ב-tree-shaking, מאחר שכל הודעה היא export עצמאי. במבחן הביצועים של Svelte + Vite, זה עובד כמובטח.

    בהגדרות אחרות, זה לא עבד. בהרצה שלנו ב-Next.js, הדפים של Paraglide שוקלים 14 KB יותר מאפליקציית הבסיס, בעוד ש-next-intlayer מוסיפה רק 0.3 KB. הרצות קודמות ב-TanStack Start הראו שהודעות מדפים אחרים הגיעו גם הן ל-bundle של הנתיב.

    Tree shaking תלוי בבנדלר שלכם (Turbopack, Rolldown, Rollup), באופן ייבוא ההודעות (import { m } לעומת import * as m), ובניתוח תופעות לוואי (side-effect analysis). אם אתם בוחרים ב-Paraglide בשל גודלה, פתחו את ה-bundle visualizer ובדקו שזה אכן מתקיים באפליקציה שלכם.

    ללא טעינה דינמית

    זוהי המגבלה המבנית. ל-Paraglide אין דרך לטעון שפה אחת בכל פעם: כל פונקציית הודעה מייבאת סטטית את המימוש של כל שפה, ולכן כל השפות נכללות ב-bundle הלקוח שלכם.

    עם 2 שפות, חצי מנפח התרגומים שלכם מבוזבז, מה שתואם את דליפת השפות של כ-50% שנמדדה למעלה. עם 10 שפות, 90% מבוזבז. עם 30 שפות, 97%.

    הגרף שלהלן מעריך את נפח הנתונים עבור אפליקציה תיאורטית של 1 עד 10 דפים ב-1 עד 10 שפות, עם כ-30 KB טקסט לכל דף. פיצול תוכן פר נתיב מסיר ציר אחד, טעינתו באופן דינמי פר שפה מסירה את הציר השני, ורק השילוב ביניהם שומר על נפח נתונים שטוח ומינימלי. Paraglide מכסה במקרה הטוב את הציר הראשון, ולעולם לא את השני.

    דליפת תוכן תיאורטית לפי ארכיטקטורה

    מעבר לטעינה דינמית לא יפתור זאת: עם פונקציה אחת לכל הודעה, טעינה עצלה של כל אחת בנפרד תגרום לאלפי בקשות רשת.

    Intlayer מאפשרת לכם לבחור, גלובלית או פר מילון:

    intlayer.config.ts
    import { Locales, type IntlayerConfig } from "intlayer";
    
    const config: IntlayerConfig = {
      internationalization: {
        locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],
        defaultLocale: Locales.ENGLISH,
      },
      dictionary: {
        importMode: "dynamic", // 'static' | 'dynamic' | 'fetch'
      },
    };
    
    export default config;
    
    importModeמה נשלח ללקוחלעומת Paraglide
    staticכל השפות של המילונים שבהם הדף משתמשתיאורטית אותו תוכן
    dynamicרק השפה הנוכחית, בטעינה עצלה (lazy-loaded) פר מילוןקטן פי N עם N שפות
    fetchרק השפה הנוכחית, נמשכת מ-Live Sync APIקטן פי N עם N שפות

    עם טרנספורמציית הבנייה ו-importMode: 'static', Intlayer טוענת, בתיאוריה, בדיוק את אותו התוכן כמו Paraglide. עם 'dynamic' או 'fetch', היא טוענת אך ורק את מה שהשפה הנוכחית צריכה: עבור אפליקציה ב-N שפות, נפח התרגום קטן פי N מזה של Paraglide.

    היכן Paraglide עדיין מתאימה

    אם ה-stack שלכם הוא Svelte עם Vite ואתם תומכים בשתיים או שלוש שפות, tree shaking עובד כמובטח והתקורה של השפות נשארת קטנה.

    אם הצוות שלכם כבר משתמש באקוסיסטם של inlang (כגון Fink, Sherlock, פלאגינים של message format), Paraglide משתלבת בו באופן טבעי.

    נסו זאת באפליקציה שלכם

    בדקו את נפח הנתונים ודליפת השפות באפליקציה הפעילה שלכם באמצעות i18n SEO Scanner החינמי:

    להגדרת Intlayer:

    bash
    npx intlayer init --interactive
    

    לקריאה נוספת

    כדי להבין מהיכן הגיעו ספריות אלו, קראו על ההיסטוריה של i18n ב-JavaScript.

    תגובות

    עדיין אין תגובות. היו הראשונים לשתף את מחשבותיכם.

    פוסטים קשורים

    פוסטים אחרונים